Changed the autocomplete from jQuery to Bootstrap.typeahead

Added DeepLabel factory to get values from inside nested json objects.
Split SalaryDeductionController into its own file.
This commit is contained in:
Tanshu 2012-11-28 21:36:17 +05:30
parent 105f72f50f
commit 5a28d9717b
24 changed files with 226 additions and 204 deletions

View File

@ -12,7 +12,7 @@
<label for="txtEmployee" class="control-label">Employee</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtEmployee" resource="Employee" model="info.Employee"
<input type="text" ng-autocomplete id="txtEmployee" resource="Employee" label="Name" model="info.Employee"
value="{{info.Employee.Name}}"/>
<button class="btn btn-info" ng-click="show()">Show</button>
</div>

View File

@ -6,7 +6,7 @@
<div class="modal-body">
<div class=" control-group">
<label for="editBatch">Product</label>
<input type="text" ng-autocomplete id="editBatch" resource="Batch" model="edit.Batch" value="{{edit.Batch.Product.Name}}"/>
<input type="text" ng-autocomplete id="editBatch" resource="Batch" label="Name" model="edit.Batch" value="{{edit.Batch.Product.Name}}"/>
<label for="editQuantity">Quantity</label>
<input type="text" id="editQuantity" autocomplete="off" class="non-search-box inline"
placeholder="Quantity" ng-model="edit.Quantity"/>

View File

@ -62,7 +62,7 @@
<label for="txtBatch" class="control-label">Product</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtBatch" resource="Batch" model="batch" value="{{batch.Name}}"/>
<input type="text" ng-autocomplete id="txtBatch" resource="Batch" label="Name" model="batch" value="{{batch.Name}}"/>
<input type="text" id="txtQuantity" class="span2" autocomplete="off" placeholder="Quantity"
ng-model="quantity"/>
<button class="btn btn-info" ng-click="addInventory()">Add <i class="icon-plus-sign icon-white"></i>

View File

@ -6,7 +6,7 @@
<div class="modal-body">
<div class=" control-group">
<label for="editAccount">Account</label>
<input type="text" ng-autocomplete id="editAccount" resource="Account" model="edit.Ledger"
<input type="text" ng-autocomplete id="editAccount" resource="Account" label="Name" model="edit.Ledger"
value="{{edit.Ledger.Name}}"/>
<label for="editAmount">Amount</label>

View File

@ -15,7 +15,7 @@
<option value="1">Dr</option>
<option value="-1">Cr</option>
</select>
<input type="text" ng-autocomplete id="txtLedger" resource="Account" model="ledger" value="{{ledger.Name}}"/>
<input type="text" ng-autocomplete id="txtLedger" resource="Account" label="Name" model="ledger" value="{{ledger.Name}}"/>
<div class="input-prepend">
<span class="add-on"></span><input type="text" id="txtAmount" class="span2" autocomplete="off"
placeholder="Amount" ng-model="amount"/>

View File

@ -12,7 +12,7 @@
<label for="txtLedger" class="control-label">Ledger</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtLedger" resource="Account" model="info.Ledger" value="{{info.Ledger.Name}}"/>
<input type="text" ng-autocomplete id="txtLedger" resource="Account" label="Name" model="info.Ledger" value="{{info.Ledger.Name}}"/>
<button class="btn btn-info" ng-click="show()">Show <i class="icon-eye-open icon-white"></i></button>
</div>
</div>

View File

@ -6,7 +6,7 @@
<div class="modal-body">
<div class=" control-group">
<label for="editAccount">Account</label>
<input type="text" ng-autocomplete id="editAccount" resource="Account" model="edit.Ledger" value="{{edit.Ledger.Name}}"/>
<input type="text" ng-autocomplete id="editAccount" resource="Account" label="Name" model="edit.Ledger" value="{{edit.Ledger.Name}}"/>
<label for="editAmount">Amount</label>
<div class="input-prepend input-append">

View File

@ -23,7 +23,7 @@
<label for="txtLedger" class="control-label">Account</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtLedger" resource="Account" model="ledger" value="{{ledger.Name}}"/>
<input type="text" ng-autocomplete id="txtLedger" resource="Account" label="Name" model="ledger" value="{{ledger.Name}}"/>
<div class=" input-prepend">
<span class="add-on"></span>
<input type="text" id="txtAmount" class="span2" autocomplete="off"

View File

@ -12,7 +12,7 @@
<label for="txtProduct" class="control-label">Ledger</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtProduct" resource="Product" model="info.Product"
<input type="text" ng-autocomplete id="txtProduct" resource="Product" label="Name" model="info.Product"
value="{{info.Product.Name}}"/>
<button class="btn btn-info" ng-click="show()">Show <i class="icon-eye-open icon-white"></i></button>
</div>

View File

@ -6,7 +6,7 @@
<div class="modal-body">
<div class=" control-group">
<label for="editBatch">Product</label>
<input type="text" ng-autocomplete id="editBatch" resource="Batch" model="edit.Batch" value="{{edit.Batch.Product.Name}}"/>
<input type="text" ng-autocomplete id="editBatch" resource="Batch" label="Name" model="edit.Batch" value="{{edit.Batch.Product.Name}}"/>
<label for="editQuantity">Quantity</label>
<input type="text" id="editQuantity" autocomplete="off" class="non-search-box inline"
placeholder="Quantity" ng-model="edit.Quantity"/>

View File

@ -12,7 +12,7 @@
<label for="txtLedger" class="control-label">Ledger</label>
<div class="controls">
<input type="text" id="txtLedger" ng-autocomplete resource="Account" model="journal.Ledger" value="{{journal.Ledger.Name}}"/>
<input type="text" id="txtLedger" ng-autocomplete resource="Account" label="Name" model="journal.Ledger" value="{{journal.Ledger.Name}}"/>
<div class="input-prepend">
<span class="add-on"></span><input type="text" id="txtLedgerAmount" class="span2"
@ -25,7 +25,7 @@
<label for="txtBatch" class="control-label">Product</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtBatch" resource="Batch" model="batch"/>
<input type="text" ng-autocomplete id="txtBatch" resource="Batch" label="Name" model="batch"/>
<input type="text" id="txtQuantity" class="span2" autocomplete="off"
placeholder="Quantity" ng-model="quantity"/>
<button class="btn btn-info" ng-click="addInventory()">Add <i class="icon-plus-sign icon-white"></i>

View File

@ -12,7 +12,7 @@
<label for="txtLedger" class="control-label">Account</label>
<div class="controls">
<input type="text" id="txtLedger" ng-autocomplete resource="Account" model="journal.Ledger" value="{{journal.Ledger.Name}}"/>
<input type="text" id="txtLedger" ng-autocomplete resource="Account" label="Name" model="journal.Ledger" value="{{journal.Ledger.Name}}"/>
<div class="input-prepend">
<span class="add-on"></span><input type="text" id="txtLedgerAmount" class="span2"
@ -25,7 +25,7 @@
<label for="txtProduct" class="control-label">Product</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtProduct" class="search-box" resource="Product" model="product"/>
<input type="text" ng-autocomplete id="txtProduct" class="search-box" resource="Product" label="Name" model="product"/>
<input type="text" id="txtQuantity" autocomplete="off" class="non-search-box inline"
placeholder="Quantity" ng-model="quantity"/>
<input type="text" id="txtPrice" autocomplete="off" class="non-search-box inline"

View File

@ -6,7 +6,7 @@
<div class="modal-body">
<div class=" control-group">
<label for="editAccount">Account</label>
<input type="text" ng-autocomplete id="editAccount" resource="Account" model="edit.Ledger" value="{{edit.Ledger.Name}}"/>
<input type="text" ng-autocomplete id="editAccount" resource="Account" label="Name" model="edit.Ledger" value="{{edit.Ledger.Name}}"/>
<label for="editAmount">Amount</label>
<div class="input-prepend input-append">

View File

@ -24,7 +24,7 @@
<label for="txtLedger" class="control-label">Account</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtLedger" resource="Account" model="ledger" value="{{ledger.Name}}"/>
<input type="text" ng-autocomplete id="txtLedger" resource="Account" label="Name" model="ledger" value="{{ledger.Name}}"/>
<div class=" input-prepend">
<span class="add-on"></span>
<input type="text" id="txtAmount" class="span2" autocomplete="off"

View File

@ -11,7 +11,7 @@
<label for="txtEmployee" class="control-label">Employee</label>
<div class="controls">
<input type="text" ng-autocomplete id="txtEmployee" resource="Employee" model="employee"
<input type="text" ng-autocomplete id="txtEmployee" resource="Employee" label="Name" model="employee"
value="{{info.Employee.Name}}"/>
<input type="text" class="span2" autocomplete="off" placeholder="Gross Salary" ng-model="grossSalary"/>
<input type="text" class="span2" autocomplete="off" placeholder="Days Worked" ng-model="daysWorked"/>

View File

@ -1,32 +1,40 @@
'use strict';
var overlord_directive = angular.module('overlord.directive', []);
overlord_directive.directive('ngAutocomplete', ['$q', '$parse', function ($q, $parse) {
overlord_directive.directive('ngAutocomplete', ['$q', '$parse', 'DeepLabel', function ($q, $parse, DeepLabel) {
return {
restrict:'A',
link:function (scope, element, attrs, ngModel) {
element.autocomplete({
source:function (request, response) {
var Entity = angular.injector(['overlord.service']).get(attrs.resource),
deferred = $q.defer();
var labels = [],
mapped = {},
deepLabel = DeepLabel;
deferred.promise.then(function (result) {
response(result);
});
Entity.autocomplete({term:request.term, count:20}, function (result) {
scope.$apply(function () {
deferred.resolve(result);
});
element.typeahead({
source:function (query, process) {
var Entity = angular.injector(['overlord.service']).get(attrs.resource);
Entity.autocomplete({term:query, count:20}, function (result) {
labels = [];
mapped = {};
$.each(result, function (i, item) {
var label = deepLabel(item, attrs.label);
mapped[label] = item
labels.push(label)
})
process(labels)
});
},
minLength:1,
autoFocus:true,
select:function (event, ui) {
items:20,
matcher:function (item) {
return true;
},
updater:function (item) {
var model = $parse(attrs.model);
var modelSetter = model.assign;
modelSetter(scope, ui.item.model);
modelSetter(scope, mapped[item]);
scope.$apply();
return item;
}
});
}
@ -161,7 +169,7 @@ overlord_directive.directive('journal-edit', function () {
};
});
overlord_directive.directive('ngEditRepeat', ['modal','$parse', function (modal, $parse) {
overlord_directive.directive('ngEditRepeat', ['modal', '$parse', function (modal, $parse) {
return {
restrict:'E',
template:'' +

View File

@ -79,8 +79,8 @@ overlord_service.factory('Product', ['$resource', function ($resource) {
overlord_service.factory('Batch', ['$resource', function ($resource) {
return $resource('/Batch', {}, {
autocomplete:{method:'GET', params:{term:''}, isArray:true}
});
autocomplete:{method:'GET', params:{term:''}, isArray:true}
});
}]);
overlord_service.factory('ProductGroup', ['$resource', function ($resource) {
@ -146,3 +146,20 @@ overlord_service.factory('AttendanceTypes', ['$resource', function ($resource) {
overlord_service.factory('EmployeeAttendance', ['$resource', function ($resource) {
return $resource('/EmployeeAttendance/:id', {id:'@Employee.LedgerID'});
}]);
overlord_service.factory('DeepLabel', function () {
return function getLabel(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
};
});

View File

@ -22,160 +22,3 @@ var EmployeeFunctionsCtrl = ['$scope', '$http', function ($scope, $http) {
}]
var SalaryDeductionCtrl = ['$scope', '$location', 'voucher', function ($scope, $location, voucher) {
$scope.voucher = voucher;
$scope.name = '';
function getOldItem(ledgerID, items) {
for (var i = 0, l = items.length; i < l; i++) {
if (items[i].Journal.Ledger.LedgerID === ledgerID) {
return journals[i];
}
}
}
function daysInMonthFunction(date) {
var months = {
Jan:1,
Feb:2,
Mar:3,
Apr:4,
May:5,
Jun:6,
Jul:7,
Aug:8,
Sep:9,
Oct:10,
Nov:11,
Dev:12
}
if (!date.match(/^\d{2}-[\w]{3}-[\d]{4}$/g)) {
return;
}
var parts = date.split("-");
return new Date(parseInt(parts[2]), months[parts[1]], 0).getDate();
}
function getEsi(grossSalary, daysWorked, daysInMonth) {
var limit = 15000,
employee_rate = .0175,
employer_rate = .0475;
var employee = (grossSalary > limit) ? 0 : Math.ceil(employee_rate * grossSalary * daysWorked / daysInMonth)
var employer = (grossSalary > limit) ? 0 : Math.ceil(employer_rate * grossSalary * daysWorked / daysInMonth)
return {ee:employee, er:employer, both:employee + employer};
}
function getPf(grossSalary, daysWorked, daysInMonth) {
var limit = 6500,
employee_rate = .12,
employer_rate = .12 + .011 + .005 + .0001;
var employee = (grossSalary > limit) ? 0 : Math.ceil(employee_rate * grossSalary * daysWorked / daysInMonth)
var employer = (grossSalary > limit) ? 0 : Math.ceil(employer_rate * grossSalary * daysWorked / daysInMonth)
return {ee:employee, er:employer, both:employee + employer};
}
$scope.add = function () {
var oldJournal = getOldItem($scope.employee.LedgerID, this.voucher.SalaryDeductions),
grossSalary = parseInt($scope.grossSalary),
daysWorked = parseInt($scope.daysWorked),
daysInMonth = daysInMonthFunction($scope.voucher.Date),
esi = getEsi(grossSalary, daysWorked, daysInMonth),
pf = getPf(grossSalary, daysWorked, daysInMonth);
if (typeof oldJournal !== 'undefined') {
$scope.toasts.push({Type:'Error', Message:'Employee has already been added!'});
} else {
this.voucher.SalaryDeductions.push(
{
Journal:{Ledger:$scope.employee},
GrossSalary:grossSalary,
DaysWorked:daysWorked,
EsiEmployee:esi.ee,
PfEmployee:pf.ee,
EsiEmployer:esi.er,
PfEmployer:pf.er
}
)
;
}
delete $scope.employee;
delete $scope.grossSalary;
delete $scope.daysWorked;
$('#txtEmployee').val('');
$('#txtEmployee').focus();
};
$scope.remove = function (deduction) {
var index = this.voucher.SalaryDeductions.indexOf(deduction);
this.voucher.SalaryDeductions.splice(index, 1);
};
$scope.preventAlteration = function (voucher) {
if (typeof $scope.perms === 'undefined') {
return false;
} else if (typeof voucher.VoucherID === 'undefined') {
return !$scope.perms['Salary Deduction'];
} else if (voucher.Posted && !$scope.perms['Edit Posted Vouchers']) {
return true;
} else if (voucher.User.UserID != $scope.auth.UserID && !$scope.perms["Edit Other User's Vouchers"]) {
return true;
} else {
return false;
}
};
$scope.get = function (voucherid) {
$scope.voucher = Voucher.get({VoucherID:voucherid}, function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
$scope.save = function () {
$scope.voucher.$save({type:'Salary Deduction'}, function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
$location.path('/SalaryDeduction/' + u.VoucherID);
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
$scope.delete = function () {
$scope.voucher.$delete(function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
$location.path('/SalaryDeduction').replace();
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
$scope.post = function () {
$scope.voucher.$post(function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
}]
SalaryDeductionCtrl.resolve = {
voucher:['$q', '$route', 'Voucher', function ($q, $route, Voucher) {
var deferred = $q.defer();
var id = $route.current.params.id;
var successCb = function (result) {
deferred.resolve(result);
};
if (typeof id === 'undefined') {
Voucher.get({type:'Salary Deduction'}, successCb);
} else {
Voucher.get({id:id}, successCb);
}
return deferred.promise;
}]
};

View File

@ -0,0 +1,158 @@
'use strict';
var SalaryDeductionCtrl = ['$scope', '$location', 'voucher', function ($scope, $location, voucher) {
$scope.voucher = voucher;
$scope.name = '';
function getOldItem(ledgerID, items) {
for (var i = 0, l = items.length; i < l; i++) {
if (items[i].Journal.Ledger.LedgerID === ledgerID) {
return items[i];
}
}
}
function daysInMonthFunction(date) {
var months = {
Jan:1,
Feb:2,
Mar:3,
Apr:4,
May:5,
Jun:6,
Jul:7,
Aug:8,
Sep:9,
Oct:10,
Nov:11,
Dev:12
}
if (!date.match(/^\d{2}-[\w]{3}-[\d]{4}$/g)) {
return;
}
var parts = date.split("-");
return new Date(parseInt(parts[2]), months[parts[1]], 0).getDate();
}
function getEsi(grossSalary, daysWorked, daysInMonth) {
var limit = 15000,
employee_rate = .0175,
employer_rate = .0475;
var employee = (grossSalary > limit) ? 0 : Math.ceil(employee_rate * grossSalary * daysWorked / daysInMonth)
var employer = (grossSalary > limit) ? 0 : Math.ceil(employer_rate * grossSalary * daysWorked / daysInMonth)
return {ee:employee, er:employer, both:employee + employer};
}
function getPf(grossSalary, daysWorked, daysInMonth) {
var limit = 6500,
employee_rate = .12,
employer_rate = .12 + .011 + .005 + .0001;
var employee = (grossSalary > limit) ? 0 : Math.ceil(employee_rate * grossSalary * daysWorked / daysInMonth)
var employer = (grossSalary > limit) ? 0 : Math.ceil(employer_rate * grossSalary * daysWorked / daysInMonth)
return {ee:employee, er:employer, both:employee + employer};
}
$scope.add = function () {
var oldJournal = getOldItem($scope.employee.LedgerID, this.voucher.SalaryDeductions),
grossSalary = parseInt($scope.grossSalary),
daysWorked = parseInt($scope.daysWorked),
daysInMonth = daysInMonthFunction($scope.voucher.Date),
esi = getEsi(grossSalary, daysWorked, daysInMonth),
pf = getPf(grossSalary, daysWorked, daysInMonth);
if (typeof oldJournal !== 'undefined') {
$scope.toasts.push({Type:'Error', Message:'Employee has already been added!'});
} else {
this.voucher.SalaryDeductions.push(
{
Journal:{Ledger:$scope.employee},
GrossSalary:grossSalary,
DaysWorked:daysWorked,
EsiEmployee:esi.ee,
PfEmployee:pf.ee,
EsiEmployer:esi.er,
PfEmployer:pf.er
}
)
;
}
delete $scope.employee;
delete $scope.grossSalary;
delete $scope.daysWorked;
$('#txtEmployee').val('');
$('#txtEmployee').focus();
};
$scope.remove = function (deduction) {
var index = this.voucher.SalaryDeductions.indexOf(deduction);
this.voucher.SalaryDeductions.splice(index, 1);
};
$scope.preventAlteration = function (voucher) {
if (typeof $scope.perms === 'undefined') {
return false;
} else if (typeof voucher.VoucherID === 'undefined') {
return !$scope.perms['Salary Deduction'];
} else if (voucher.Posted && !$scope.perms['Edit Posted Vouchers']) {
return true;
} else if (voucher.User.UserID != $scope.auth.UserID && !$scope.perms["Edit Other User's Vouchers"]) {
return true;
} else {
return false;
}
};
$scope.get = function (voucherid) {
$scope.voucher = Voucher.get({VoucherID:voucherid}, function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
$scope.save = function () {
$scope.voucher.$save({type:'Salary Deduction'}, function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
$location.path('/SalaryDeduction/' + u.VoucherID);
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
$scope.delete = function () {
$scope.voucher.$delete(function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
$location.path('/SalaryDeduction').replace();
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
$scope.post = function () {
$scope.voucher.$post(function (u, putResponseHeaders) {
$scope.toasts.push({Type:'Success', Message:''});
}, function (data, status) {
$scope.toasts.push({Type:'Error', Message:data.data});
});
};
}]
SalaryDeductionCtrl.resolve = {
voucher:['$q', '$route', 'Voucher', function ($q, $route, Voucher) {
var deferred = $q.defer();
var id = $route.current.params.id;
var successCb = function (result) {
deferred.resolve(result);
};
if (typeof id === 'undefined') {
Voucher.get({type:'Salary Deduction'}, successCb);
} else {
Voucher.get({id:id}, successCb);
}
return deferred.promise;
}]
};

View File

@ -40,6 +40,7 @@
<script src="/script/purchase.js"> </script>
<script src="/script/purchase-return.js"> </script>
<script src="/script/issue.js"> </script>
<script src="/script/salary-deduction.js"> </script>
<script src="/script/employee-attendance.js"> </script>
<script src="/script/employee-functions.js"> </script>

View File

@ -102,7 +102,7 @@ def show_term(request):
def ledger_list(type=None, filter=None, count=None):
list = []
for index, item in enumerate(LedgerBase.list(type, filter)):
list.append({'label': item.name, 'model': {'LedgerID': item.id, 'Name': item.name}})
list.append({'LedgerID': item.id, 'Name': item.name})
if count is not None and index == count - 1:
break
return list

View File

@ -105,10 +105,8 @@ def show_term(request):
list = []
for index, item in enumerate(LedgerBase.list(10, filter)):
list.append({'label': item.name,
'model': {'LedgerID': item.id, 'Name': item.name, 'Designation': item.designation,
'CostCenter': {'CostCenterID': item.costcenter.id,
'Name': item.costcenter.name}}})
list.append({'LedgerID': item.id, 'Name': item.name, 'Designation': item.designation,
'CostCenter': {'CostCenterID': item.costcenter.id, 'Name': item.costcenter.name}})
if count is not None and index == count - 1:
break
return list

View File

@ -102,8 +102,7 @@ def show_term(request):
count = None if count is None or count == '' else int(count)
list = []
for index, item in enumerate(Product.list(filter)):
list.append({'label': item.full_name,
'model': {'ProductID': item.id, 'Name': item.full_name, 'Price': item.price}})
list.append({'ProductID': item.id, 'Name': item.full_name, 'Price': item.price})
if count is not None and index == count - 1:
break
return list

View File

@ -13,11 +13,9 @@ def batch_term(request):
text = "{0} ({1}) {2:.2f}@{3:.2f} from {4}".format(item.product.name, item.product.units,
item.quantity_remaining,
item.rate, item.name)
list.append({'label': text,
'model': {
'Product': {'ProductID': item.product.id, 'Name': item.product.name, 'Units': item.product.units},
'BatchID': item.id, 'Name': text, 'QuantityRemaining': item.quantity_remaining,
'Rate': item.rate, 'Tax': item.tax, 'Discount': item.discount}})
list.append({'BatchID': item.id, 'Name': text, 'QuantityRemaining': item.quantity_remaining,
'Rate': item.rate, 'Tax': item.tax, 'Discount': item.discount,
'Product': {'ProductID': item.product.id, 'Name': item.product.name, 'Units': item.product.units}})
if count is not None and index == count - 1:
break
return list