PageSourceSearch

https://app.pymo.uy/js/controllers/items-sales-report.js?v=4.0.8

js pymo.uy collected 2026-10-04 22:29:18 UTC 8,515 bytes, 262 lines download raw bytes

1materialAdmin
2
3  //=================================================
4  // ITEMS SALES REPORT
5  //=================================================
6  .controller('itemsSalesReportCtrl', function ($scope, growlService, Item, Utils, Session) {
7    var _this = this;
8
9    $scope.primaryButtonHandler = (addonName) => Utils.requestAddon(addonName);
10    const user = Session.get();
11    $scope.isSalesReportEnabled = user?.company?.addons?.includes("stock");
12
13    // Initialize variables
14    _this.salesData = [];
15    _this.loading = false;
16    _this.searched = false;
17    _this.error = null;
18    _this.period = {};
19
20    /*********** DATE **************/
21    _this.dateOptions = {
22        startingDay: 1,
23        showWeeks: "false",
24    };
25
26    _this.formats = ["dd/MM/yyyy"];
27    _this.format = _this.formats[0];
28
29    _this.status = {};
30
31    _this.open = function ($event, opened) {
32        $event.preventDefault();
33        $event.stopPropagation();
34        _this.status[opened] = true;
35    };
36
37    _this.filters = {
38      start_date: new Date(),
39      end_date: new Date()
40    };
41
42    // Selection state for predefined date range buttons
43    _this.thisWeekSelected = false;
44    _this.thisMonthSelected = false;
45    _this.previousMonthSelected = false;
46    _this.thisYearSelected = false;
47
48    // Clear all selection states
49    _this.clearSelectionStates = function() {
50      _this.thisWeekSelected = false;
51      _this.thisMonthSelected = false;
52      _this.previousMonthSelected = false;
53      _this.thisYearSelected = false;
54    };
55
56    // Date change handler
57    _this.dateChanged = function() {
58      _this.error = null;
59      if (_this.filters.start_date && _this.filters.end_date) {
60        var startDate = new Date(_this.filters.start_date);
61        var endDate = new Date(_this.filters.end_date);
62
63        if (startDate > endDate) {
64          _this.error = 'La fecha de inicio no puede ser mayor que la fecha de fin.';
65          return;
66        }
67
68        // Check if date range is not too long (optional validation)
69        var diffTime = Math.abs(endDate - startDate);
70        var diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
71
72        if (diffDays > 365) {
73          growlService.growl('El período seleccionado es muy largo. Se recomienda usar períodos menores a un año.', 'warning');
74        }
75        _this.loadReport();
76      }
77    };
78
79    // Predefined date ranges
80    _this.setThisWeek = function() {
81      _this.clearSelectionStates();
82      _this.thisWeekSelected = true;
83
84      var today = new Date();
85      var firstDayOfWeek = new Date(today.setDate(today.getDate() - today.getDay() + 1)); // Monday
86      var lastDayOfWeek = new Date(today.setDate(today.getDate() - today.getDay() + 7)); // Sunday
87
88      _this.filters.start_date = firstDayOfWeek.toISOString().split('T')[0];
89      _this.filters.end_date = lastDayOfWeek.toISOString().split('T')[0];
90      _this.dateChanged();
91    };
92
93    _this.setThisMonth = function() {
94      _this.clearSelectionStates();
95      _this.thisMonthSelected = true;
96
97      var today = new Date();
98      var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
99      var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
100
101      _this.filters.start_date = firstDay.toISOString().split('T')[0];
102      _this.filters.end_date = lastDay.toISOString().split('T')[0];
103      _this.dateChanged();
104    };
105
106    _this.setPreviousMonth = function() {
107      _this.clearSelectionStates();
108      _this.previousMonthSelected = true;
109
110      var today = new Date();
111      var firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1);
112      var lastDay = new Date(today.getFullYear(), today.getMonth(), 0);
113
114      _this.filters.start_date = firstDay.toISOString().split('T')[0];
115      _this.filters.end_date = lastDay.toISOString().split('T')[0];
116      _this.dateChanged();
117    };
118
119    _this.setThisYear = function() {
120      _this.clearSelectionStates();
121      _this.thisYearSelected = true;
122
123      var today = new Date();
124      var firstDay = new Date(today.getFullYear(), 0, 1);
125      var lastDay = new Date(today.getFullYear(), 11, 31);
126
127      _this.filters.start_date = firstDay.toISOString().split('T')[0];
128      _this.filters.end_date = lastDay.toISOString().split('T')[0];
129      _this.dateChanged();
130    };
131
132    // Load sales report
133    _this.loadReport = function() {
134      if (!$scope.isSalesReportEnabled) {
135        growlService.growlError('El complemento se encuentra deshabilitado');
136        return;
137      }
138
139      if (!_this.filters.start_date || !_this.filters.end_date) {
140        growlService.growlError('Seleccione las fechas de inicio y fin.');
141        return;
142      }
143
144      _this.loading = true;
145      _this.error = null;
146      _this.searched = true;
147
148      var params = {
149        start_date: _this.filters.start_date,
150        end_date: _this.filters.end_date
151      };
152
153      Item.salesReport(params)
154        .then(function(response) {
155          _this.salesData = response.sales_report || [];
156          _this.period = response.period || {};
157        })
158        .catch(function(error) {
159          console.error('Error loading sales report:', error);
160
161          var errorMessage = 'Error al cargar el reporte de ventas.';
162          if (error.data && error.data.error) {
163            errorMessage = error.data.error;
164          } else if (error.status === 400) {
165            errorMessage = 'Parámetros de fecha inválidos.';
166          } else if (error.status === 401) {
167            errorMessage = 'No tiene permisos para acceder a este reporte.';
168          } else if (error.status >= 500) {
169            errorMessage = 'Error interno del servidor. Intente nuevamente más tarde.';
170          }
171
172          _this.error = errorMessage;
173          growlService.growlError(errorMessage);
174        })
175        .finally(function() {
176          _this.loading = false;
177        });
178    };
179
180    // Calculate totals
181    _this.getTotalQuantity = function() {
182      return _this.salesData.reduce(function(sum, item) {
183        return sum + (parseFloat(item.total_quantity_sold) || 0);
184      }, 0);
185    };
186
187    _this.getTotalAmount = function() {
188      return _this.salesData.reduce(function(sum, item) {
189        return sum + (parseFloat(item.total_amount) || 0);
190      }, 0);
191    };
192
193    _this.getAveragePrice = function() {
194      var totalQuantity = _this.getTotalQuantity();
195      var totalAmount = _this.getTotalAmount();
196
197      if (totalQuantity === 0) return 0;
198      return totalAmount / totalQuantity;
199    };
200
201    // Export to Excel
202    _this.exportToExcel = function() {
203      if (_this.salesData.length === 0) {
204        growlService.growlWarning('No hay datos para exportar.');
205        return;
206      }
207
208      var createXLSLFormatObj = [];
209
210      // Title
211      createXLSLFormatObj.push(['Reporte de Ventas por Artículo']);
212
213      // Period
214      var startDateFormatted = _this.period.start_date ? new Date(_this.period.start_date).toLocaleDateString('es-ES') : '';
215      var endDateFormatted = _this.period.end_date ? new Date(_this.period.end_date).toLocaleDateString('es-ES') : '';
216      createXLSLFormatObj.push(['DESDE', startDateFormatted, 'HASTA', endDateFormatted]);
217
218      // Empty row
219      createXLSLFormatObj.push([]);
220
221      // Headers
222      var xlsHeader = ['Tipo', 'ID Artículo', 'Nombre del Artículo', 'SKU', 'Cantidad Vendida', 'Monto Total'];
223      createXLSLFormatObj.push(xlsHeader);
224
225      // Data rows
226      _this.salesData.forEach(function(item) {
227        var row = [
228          item.is_product ? 'Producto' : 'Servicio',
229          item.item_id,
230          item.item_name,
231          item.item_code || '-',
232          parseFloat(item.total_quantity_sold) || 0,
233          parseFloat(item.total_amount) || 0
234        ];
235        createXLSLFormatObj.push(row);
236      });
237
238      // Empty row
239      createXLSLFormatObj.push([]);
240
241      // Totals
242      createXLSLFormatObj.push(['TOTALES', '', '', '', _this.getTotalQuantity(), _this.getTotalAmount()]);
243
244      // Generate filename
245      var filename = 'reporte-ventas-items-' + startDateFormatted.replace(/\//g, '-') + '-a-' + endDateFormatted.replace(/\//g, '-') + '.xlsx';
246
247      // Create workbook
248      var wb = XLSX.utils.book_new();
249      var ws = XLSX.utils.aoa_to_sheet(createXLSLFormatObj);
250
251      // Add worksheet to workbook
252      XLSX.utils.book_append_sheet(wb, ws, 'Reporte de Ventas');
253
254      // Download file
255      XLSX.writeFile(wb, filename);
256
257      growlService.growl('Reporte exportado exitosamente.', 'success');
258    };
259
260    _this.setThisWeek();
261
262  });

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.