@ -0,0 +1,41 @@ |
|||||
|
Export Product Stock in Excel v15 |
||||
|
================================= |
||||
|
This module helps you to take current stock report for all products in each warehouse. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
* No additional configurations needed |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developers: Cybrosys Techno Solutions odoo@cybrosys.com |
||||
|
Version 15: Midilaj V K @cybrosys |
||||
|
|
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
Bug Tracker |
||||
|
----------- |
||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
||||
|
|
||||
|
Maintainer |
||||
|
========== |
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com |
||||
|
|
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit `Our Website <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
||||
|
|
||||
|
|
@ -0,0 +1,24 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
|
||||
|
from . import models |
||||
|
from . import controllers |
@ -0,0 +1,54 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2021-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
|
||||
|
{ |
||||
|
'name': 'Export Product Stock in Excel', |
||||
|
'version': '15.0.1.0.0', |
||||
|
'live_test_url': 'https://www.youtube.com/watch?v=9ae4GkApHQM', |
||||
|
'summary': "Current Stock Report for all Products in each Warehouse", |
||||
|
'description': "Current Stock Report for all Products in each Warehouse, Odoo 13,Odoo13", |
||||
|
'category': 'Warehouse', |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'website': 'https://www.cybrosys.com', |
||||
|
'depends': [ |
||||
|
'base', |
||||
|
'stock', |
||||
|
'sale', |
||||
|
'purchase', |
||||
|
], |
||||
|
'data': [ |
||||
|
'views/wizard_view.xml', |
||||
|
'security/ir.model.access.csv', |
||||
|
], |
||||
|
'images': ['static/description/banner.png'], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'export_stockinfo_xls/static/src/js/action_manager.js', |
||||
|
], |
||||
|
}, |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'auto_install': False, |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
from . import main |
@ -0,0 +1,57 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
|
||||
|
import json |
||||
|
from odoo import http |
||||
|
from odoo.http import content_disposition, request |
||||
|
from odoo.addons.web.controllers.main import _serialize_exception |
||||
|
from odoo.tools import html_escape |
||||
|
|
||||
|
|
||||
|
class XLSXReportController(http.Controller): |
||||
|
|
||||
|
@http.route('/xlsx_reports', type='http', auth='user', methods=['POST'], csrf=False) |
||||
|
def get_report_xlsx(self, model, options, output_format, report_name, **kw): |
||||
|
uid = request.session.uid |
||||
|
report_obj = request.env[model].with_user(uid) |
||||
|
options = json.loads(options) |
||||
|
token = 'dummy-because-api-expects-one' |
||||
|
try: |
||||
|
if output_format == 'xlsx': |
||||
|
response = request.make_response( |
||||
|
None, |
||||
|
headers=[ |
||||
|
('Content-Type', 'application/vnd.ms-excel'), |
||||
|
('Content-Disposition', content_disposition(report_name + '.xlsx')) |
||||
|
] |
||||
|
) |
||||
|
report_obj.get_xlsx_report(options, response) |
||||
|
response.set_cookie('fileToken', token) |
||||
|
return response |
||||
|
except Exception as e: |
||||
|
se = _serialize_exception(e) |
||||
|
error = { |
||||
|
'code': 200, |
||||
|
'message': 'Odoo Server Error', |
||||
|
'data': se |
||||
|
} |
||||
|
return request.make_response(html_escape(json.dumps(error))) |
@ -0,0 +1,11 @@ |
|||||
|
## Module <export_stockinfo_xls> |
||||
|
|
||||
|
#### 04.10.2021 |
||||
|
#### Version 15.0.1.0.0 |
||||
|
#### ADD |
||||
|
Initial Commit Export Product Stock in Excel |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,24 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
|
||||
|
from . import res_partner |
||||
|
from . import wizard |
@ -0,0 +1,41 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
|
||||
|
from odoo import models, fields |
||||
|
|
||||
|
|
||||
|
class Partner(models.Model): |
||||
|
_inherit = 'res.partner' |
||||
|
|
||||
|
supplier_id = fields.Many2many('wizard.stock.history', 'supp_wiz_rel', 'wiz', 'supp', invisible=True) |
||||
|
|
||||
|
|
||||
|
class Category(models.Model): |
||||
|
_inherit = 'product.category' |
||||
|
|
||||
|
obj = fields.Many2many('wizard.stock.history', 'categ_wiz_rel', 'wiz', 'categ', invisible=True) |
||||
|
|
||||
|
|
||||
|
class Warehouse(models.Model): |
||||
|
_inherit = 'stock.warehouse' |
||||
|
|
||||
|
obj = fields.Many2many('wizard.stock.history', 'wh_wiz_rel', 'wiz', 'wh', invisible=True) |
@ -0,0 +1,249 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################# |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Midilaj (<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################# |
||||
|
import time |
||||
|
from datetime import date, datetime |
||||
|
import pytz |
||||
|
import json |
||||
|
import datetime |
||||
|
import io |
||||
|
from odoo import api, fields, models, _ |
||||
|
from odoo.tools import date_utils |
||||
|
try: |
||||
|
from odoo.tools.misc import xlsxwriter |
||||
|
except ImportError: |
||||
|
import xlsxwriter |
||||
|
|
||||
|
|
||||
|
class StockReport(models.TransientModel): |
||||
|
_name = "wizard.stock.history" |
||||
|
_description = "Current Stock History" |
||||
|
|
||||
|
warehouse = fields.Many2many('stock.warehouse', 'wh_wiz_rel', 'wh', 'wiz', string='Warehouse', required=True) |
||||
|
category = fields.Many2many('product.category', 'categ_wiz_rel', 'categ', 'wiz', string='Warehouse') |
||||
|
|
||||
|
def export_xls(self): |
||||
|
data = { |
||||
|
'ids': self.ids, |
||||
|
'model': self._name, |
||||
|
'warehouse': self.warehouse.ids, |
||||
|
'category': self.category.ids, |
||||
|
|
||||
|
} |
||||
|
return { |
||||
|
'type': 'ir.actions.report', |
||||
|
'data': {'model': 'wizard.stock.history', |
||||
|
'options': json.dumps(data, default=date_utils.json_default), |
||||
|
'output_format': 'xlsx', |
||||
|
'report_name': 'Current Stock History', |
||||
|
}, |
||||
|
'report_type': 'stock_xlsx' |
||||
|
} |
||||
|
|
||||
|
def get_warehouse(self, data): |
||||
|
wh = data.warehouse.mapped('id') |
||||
|
obj = self.env['stock.warehouse'].search([('id', 'in', wh)]) |
||||
|
l1 = [] |
||||
|
l2 = [] |
||||
|
for j in obj: |
||||
|
l1.append(j.name) |
||||
|
l2.append(j.id) |
||||
|
return l1, l2 |
||||
|
|
||||
|
def get_lines(self, data, warehouse): |
||||
|
lines = [] |
||||
|
categ_id = data.mapped('id') |
||||
|
if categ_id: |
||||
|
categ_products = self.env['product.product'].search([('categ_id', 'in', categ_id)]) |
||||
|
|
||||
|
else: |
||||
|
categ_products = self.env['product.product'].search([]) |
||||
|
product_ids = tuple([pro_id.id for pro_id in categ_products]) |
||||
|
sale_query = """ |
||||
|
SELECT sum(s_o_l.product_uom_qty) AS product_uom_qty, s_o_l.product_id FROM sale_order_line AS s_o_l |
||||
|
JOIN sale_order AS s_o ON s_o_l.order_id = s_o.id |
||||
|
WHERE s_o.state IN ('sale','done') |
||||
|
AND s_o.warehouse_id = %s |
||||
|
AND s_o_l.product_id in %s group by s_o_l.product_id""" |
||||
|
purchase_query = """ |
||||
|
SELECT sum(p_o_l.product_qty) AS product_qty, p_o_l.product_id FROM purchase_order_line AS p_o_l |
||||
|
JOIN purchase_order AS p_o ON p_o_l.order_id = p_o.id |
||||
|
INNER JOIN stock_picking_type AS s_p_t ON p_o.picking_type_id = s_p_t.id |
||||
|
WHERE p_o.state IN ('purchase','done') |
||||
|
AND s_p_t.warehouse_id = %s AND p_o_l.product_id in %s group by p_o_l.product_id""" |
||||
|
params = warehouse, product_ids if product_ids else (0, 0) |
||||
|
self._cr.execute(sale_query, params) |
||||
|
sol_query_obj = self._cr.dictfetchall() |
||||
|
self._cr.execute(purchase_query, params) |
||||
|
pol_query_obj = self._cr.dictfetchall() |
||||
|
for obj in categ_products: |
||||
|
sale_value = 0 |
||||
|
purchase_value = 0 |
||||
|
for sol_product in sol_query_obj: |
||||
|
if sol_product['product_id'] == obj.id: |
||||
|
sale_value = sol_product['product_uom_qty'] |
||||
|
for pol_product in pol_query_obj: |
||||
|
if pol_product['product_id'] == obj.id: |
||||
|
purchase_value = pol_product['product_qty'] |
||||
|
virtual_available = obj.with_context({'warehouse': warehouse}).virtual_available |
||||
|
outgoing_qty = obj.with_context({'warehouse': warehouse}).outgoing_qty |
||||
|
incoming_qty = obj.with_context({'warehouse': warehouse}).incoming_qty |
||||
|
available_qty = virtual_available + outgoing_qty - incoming_qty |
||||
|
value = available_qty * obj.standard_price |
||||
|
vals = { |
||||
|
'sku': obj.default_code, |
||||
|
'name': obj.name, |
||||
|
'category': obj.categ_id.name, |
||||
|
'cost_price': obj.standard_price, |
||||
|
'available': available_qty, |
||||
|
'virtual': virtual_available, |
||||
|
'incoming': incoming_qty, |
||||
|
'outgoing': outgoing_qty, |
||||
|
'net_on_hand': obj.with_context({'warehouse': warehouse}).qty_available, |
||||
|
'total_value': value, |
||||
|
'sale_value': sale_value, |
||||
|
'purchase_value': purchase_value, |
||||
|
} |
||||
|
lines.append(vals) |
||||
|
return lines |
||||
|
|
||||
|
def get_xlsx_report(self, data, response): |
||||
|
output = io.BytesIO() |
||||
|
workbook = xlsxwriter.Workbook(output, {'in_memory': True}) |
||||
|
lines = self.browse(data['ids']) |
||||
|
d = lines.category |
||||
|
get_warehouse = self.get_warehouse(lines) |
||||
|
count = len(get_warehouse[0]) * 11 + 6 |
||||
|
comp = self.env.user.company_id.name |
||||
|
sheet = workbook.add_worksheet('Stock Info') |
||||
|
format0 = workbook.add_format({'font_size': 20, 'align': 'center', 'bold': True}) |
||||
|
format1 = workbook.add_format({'font_size': 14, 'align': 'vcenter', 'bold': True}) |
||||
|
format11 = workbook.add_format({'font_size': 12, 'align': 'center', 'bold': True}) |
||||
|
format21 = workbook.add_format({'font_size': 10, 'align': 'center', 'bold': True}) |
||||
|
format3 = workbook.add_format({'bottom': True, 'top': True, 'font_size': 12}) |
||||
|
format4 = workbook.add_format({'font_size': 12, 'align': 'left', 'bold': True}) |
||||
|
font_size_8 = workbook.add_format({'font_size': 8, 'align': 'center'}) |
||||
|
font_size_8_l = workbook.add_format({'font_size': 8, 'align': 'left'}) |
||||
|
font_size_8_r = workbook.add_format({'font_size': 8, 'align': 'right'}) |
||||
|
red_mark = workbook.add_format({'font_size': 8, 'bg_color': 'red'}) |
||||
|
justify = workbook.add_format({'font_size': 12}) |
||||
|
format3.set_align('center') |
||||
|
justify.set_align('justify') |
||||
|
format1.set_align('center') |
||||
|
red_mark.set_align('center') |
||||
|
sheet.merge_range(1, 7, 2, 10, 'Product Stock Info', format0) |
||||
|
sheet.merge_range(3, 7, 3, 10, comp, format11) |
||||
|
w_house = ', ' |
||||
|
cat = ', ' |
||||
|
c = [] |
||||
|
d1 = d.mapped('id') |
||||
|
if d1: |
||||
|
for i in d1: |
||||
|
c.append(self.env['product.category'].browse(i).name) |
||||
|
cat = cat.join(c) |
||||
|
sheet.merge_range(4, 0, 4, 1, 'Category(s) : ', format4) |
||||
|
sheet.merge_range(4, 2, 4, 3 + len(d1), cat, format4) |
||||
|
sheet.merge_range(5, 0, 5, 1, 'Warehouse(s) : ', format4) |
||||
|
w_house = w_house.join(get_warehouse[0]) |
||||
|
sheet.merge_range(5, 2, 5, 3 + len(get_warehouse[0]), w_house, format4) |
||||
|
user = self.env['res.users'].browse(self.env.uid) |
||||
|
tz = pytz.timezone(user.tz if user.tz else 'UTC') |
||||
|
times = pytz.utc.localize(datetime.datetime.now()).astimezone(tz) |
||||
|
sheet.merge_range('A8:G8', 'Report Date: ' + str(times.strftime("%Y-%m-%d %H:%M %p")), format1) |
||||
|
sheet.merge_range(7, 7, 7, count, 'Warehouses', format1) |
||||
|
sheet.merge_range('A9:G9', 'Product Information', format11) |
||||
|
w_col_no = 6 |
||||
|
w_col_no1 = 7 |
||||
|
for i in get_warehouse[0]: |
||||
|
w_col_no = w_col_no + 11 |
||||
|
sheet.merge_range(8, w_col_no1, 8, w_col_no, i, format11) |
||||
|
w_col_no1 = w_col_no1 + 11 |
||||
|
sheet.write(9, 0, 'SKU', format21) |
||||
|
sheet.merge_range(9, 1, 9, 3, 'Name', format21) |
||||
|
sheet.merge_range(9, 4, 9, 5, 'Category', format21) |
||||
|
sheet.write(9, 6, 'Cost Price', format21) |
||||
|
p_col_no1 = 7 |
||||
|
for i in get_warehouse[0]: |
||||
|
sheet.write(9, p_col_no1, 'Available', format21) |
||||
|
sheet.write(9, p_col_no1 + 1, 'Virtual', format21) |
||||
|
sheet.write(9, p_col_no1 + 2, 'Incoming', format21) |
||||
|
sheet.write(9, p_col_no1 + 3, 'Outgoing', format21) |
||||
|
sheet.merge_range(9, p_col_no1 + 4, 9, p_col_no1 + 5, 'Net On Hand', format21) |
||||
|
sheet.merge_range(9, p_col_no1 + 6, 9, p_col_no1 + 7, 'Total Sold', format21) |
||||
|
sheet.merge_range(9, p_col_no1 + 8, 9, p_col_no1 + 9, 'Total Purchased', format21) |
||||
|
sheet.write(9, p_col_no1 + 10, 'Valuation', format21) |
||||
|
p_col_no1 = p_col_no1 + 11 |
||||
|
prod_row = 10 |
||||
|
prod_col = 0 |
||||
|
for i in get_warehouse[1]: |
||||
|
get_line = self.get_lines(d, i) |
||||
|
for each in get_line: |
||||
|
sheet.write(prod_row, prod_col, each['sku'], font_size_8) |
||||
|
sheet.merge_range(prod_row, prod_col + 1, prod_row, prod_col + 3, each['name'], font_size_8_l) |
||||
|
sheet.merge_range(prod_row, prod_col + 4, prod_row, prod_col + 5, each['category'], font_size_8_l) |
||||
|
sheet.write(prod_row, prod_col + 6, each['cost_price'], font_size_8_r) |
||||
|
prod_row = prod_row + 1 |
||||
|
break |
||||
|
prod_row = 10 |
||||
|
prod_col = 7 |
||||
|
for i in get_warehouse[1]: |
||||
|
get_line = self.get_lines(d, i) |
||||
|
for each in get_line: |
||||
|
if each['available'] < 0: |
||||
|
sheet.write(prod_row, prod_col, each['available'], red_mark) |
||||
|
else: |
||||
|
sheet.write(prod_row, prod_col, each['available'], font_size_8) |
||||
|
if each['virtual'] < 0: |
||||
|
sheet.write(prod_row, prod_col + 1, each['virtual'], red_mark) |
||||
|
else: |
||||
|
sheet.write(prod_row, prod_col + 1, each['virtual'], font_size_8) |
||||
|
if each['incoming'] < 0: |
||||
|
sheet.write(prod_row, prod_col + 2, each['incoming'], red_mark) |
||||
|
else: |
||||
|
sheet.write(prod_row, prod_col + 2, each['incoming'], font_size_8) |
||||
|
if each['outgoing'] < 0: |
||||
|
sheet.write(prod_row, prod_col + 3, each['outgoing'], red_mark) |
||||
|
else: |
||||
|
sheet.write(prod_row, prod_col + 3, each['outgoing'], font_size_8) |
||||
|
if each['net_on_hand'] < 0: |
||||
|
sheet.merge_range(prod_row, prod_col + 4, prod_row, prod_col + 5, each['net_on_hand'], red_mark) |
||||
|
else: |
||||
|
sheet.merge_range(prod_row, prod_col + 4, prod_row, prod_col + 5, each['net_on_hand'], font_size_8) |
||||
|
if each['sale_value'] < 0: |
||||
|
sheet.merge_range(prod_row, prod_col + 6, prod_row, prod_col + 7, each['sale_value'], red_mark) |
||||
|
else: |
||||
|
sheet.merge_range(prod_row, prod_col + 6, prod_row, prod_col + 7, each['sale_value'], font_size_8) |
||||
|
if each['purchase_value'] < 0: |
||||
|
sheet.merge_range(prod_row, prod_col + 8, prod_row, prod_col + 9, each['purchase_value'], red_mark) |
||||
|
else: |
||||
|
sheet.merge_range(prod_row, prod_col + 8, prod_row, prod_col + 9, each['purchase_value'], |
||||
|
font_size_8) |
||||
|
if each['total_value'] < 0: |
||||
|
sheet.write(prod_row, prod_col + 10, each['total_value'], red_mark) |
||||
|
else: |
||||
|
sheet.write(prod_row, prod_col + 10, each['total_value'], font_size_8_r) |
||||
|
prod_row = prod_row + 1 |
||||
|
prod_row = 10 |
||||
|
prod_col = prod_col + 11 |
||||
|
workbook.close() |
||||
|
output.seek(0) |
||||
|
response.stream.write(output.read()) |
||||
|
output.close() |
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 266 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,602 @@ |
|||||
|
<div class="container" style="padding: 1rem !important; margin-bottom: 1rem !important;"> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 col-md-12 col-lg-12 d-flex justify-content-between" |
||||
|
style="border-bottom: 1px solid #d5d5d5;"> |
||||
|
<div class="my-3"> |
||||
|
<img src="./assets/icons/logo.png" style="width: auto !important; height: 40px !important;"> |
||||
|
</div> |
||||
|
<div class="my-3 d-flex align-items-center"> |
||||
|
<div |
||||
|
style="background-color: #7C7BAD !important; color: #fff !important; font-weight: 600 !important; padding: 5px 15px 8px !important; margin: 0 5px !important;"> |
||||
|
<i class="fa fa-check mr-1"></i>Community |
||||
|
</div> |
||||
|
<div |
||||
|
style="background-color: #875A7B !important; color: #fff !important; font-weight: 600 !important; padding: 5px 15px 8px !important; margin: 0 5px !important;"> |
||||
|
<i class="fa fa-check mr-1"></i>Enterprise |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container" style="padding: 0rem 1.5rem 4rem !important"> |
||||
|
<div class="row" style="height: 900px !important;"> |
||||
|
<div class="col-sm-12 col-md-12 col-lg-12" |
||||
|
style="padding: 4rem 1rem !important; background-color: #714B67 !important; height: 600px !important; border-radius: 20px !important;"> |
||||
|
<h1 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #FFFFFF !important; font-size: 3.5rem !important; text-align: center !important;"> |
||||
|
Current Stock XLS</h1> |
||||
|
<p |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 300 !important; color: #FFFFFF !important; font-size: 1.4rem !important; text-align: center !important;"> |
||||
|
Current Stock Report for all Products in each Warehouse |
||||
|
</p> |
||||
|
<img src="./assets/screenshots/hero.png" class="img-responsive" width="100%" height="auto" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin-bottom: 2rem !important"> |
||||
|
<h2 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;"> |
||||
|
<i class="fa fa-compass mr-2"></i>Explore this module |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="col-md-6"> |
||||
|
<a href="#overview" style="text-decoration: none !important;"> |
||||
|
<div class="row" |
||||
|
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;"> |
||||
|
<div class="col-8"> |
||||
|
<h3 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;"> |
||||
|
Overview</h3> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;"> |
||||
|
Learn more about this module</p> |
||||
|
</div> |
||||
|
<div class="col-4 text-right d-flex justify-content-end align-items-center"> |
||||
|
<i class="fa fa-chevron-right" style="color: #714B67 !important;"></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-md-6"> |
||||
|
<a href="#features" style="text-decoration: none !important;"> |
||||
|
<div class="row" |
||||
|
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;"> |
||||
|
<div class="col-8"> |
||||
|
<h3 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;"> |
||||
|
Features</h3> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;"> |
||||
|
View features of this module</p> |
||||
|
</div> |
||||
|
<div class="col-4 text-right d-flex justify-content-end align-items-center"> |
||||
|
<i class="fa fa-chevron-right" style="color: #714B67 !important;"></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-md-6"> |
||||
|
<a href="#screenshots" style="text-decoration: none !important;"> |
||||
|
<div class="row" |
||||
|
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;"> |
||||
|
<div class="col-8"> |
||||
|
<h3 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;"> |
||||
|
Screenshots</h3> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;"> |
||||
|
See key screenshots of this module</p> |
||||
|
</div> |
||||
|
<div class="col-4 text-right d-flex justify-content-end align-items-center"> |
||||
|
<i class="fa fa-chevron-right" style="color: #714B67 !important;"></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="row" id="overview"> |
||||
|
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important"> |
||||
|
<h2 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;"> |
||||
|
<i class="fa fa-pie-chart mr-2"></i>Overview |
||||
|
</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-mg-12 pl-3"> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important; line-height: 30px !important;"> |
||||
|
This module helps to print Current Stock Report for all Products in each Warehouse with XLS</p> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="row" id="features"> |
||||
|
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important"> |
||||
|
<h2 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;"> |
||||
|
<i class="fa fa-star mr-2"></i>Features |
||||
|
</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-6 pl-3 py-3 d-flex"> |
||||
|
<div> |
||||
|
<img src="assets/icons/check.png"> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4 |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Community & Enterprise Support</h4> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Available in Odoo 14.0 Community and Enterprise.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-6 pl-3 py-3 d-flex"> |
||||
|
<div> |
||||
|
<img src="assets/icons/check.png"> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4 |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Current Stock XLS</h4> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Current Stock Report for all Products in each Warehouse.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-6 pl-3 py-3 d-flex"> |
||||
|
<div> |
||||
|
<img src="assets/icons/check.png"> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4 |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Select category for products</h4> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-6 pl-3 py-3 d-flex"> |
||||
|
<div> |
||||
|
<img src="assets/icons/check.png"> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4 |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Get your stock valuation details</h4> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-6 pl-3 py-3 d-flex"> |
||||
|
<div> |
||||
|
<img src="assets/icons/check.png"> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4 |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Negative stock will be highlighted in "red" cells.</h4> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div class="row" id="screenshots"> |
||||
|
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important"> |
||||
|
<h2 |
||||
|
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;"> |
||||
|
<i class="fa fa-image mr-2"></i>Screenshots |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="col-lg-12 my-2"> |
||||
|
<h4 class="mt-2" |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Export Stock Info Wizard</h4> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Go to Inventory -> Reports -> Current stock in Excel. Now a wizard will appear on your screen. |
||||
|
Please enter the warehouses which you want to take the report. |
||||
|
You can also select category for products(It is Optional). Then Click "Export Product with Stock |
||||
|
Info" button. Then You will get the corresponding report in XLS.</p> |
||||
|
<img src="assets/screenshots/stock_xlsx1.png" class="img-responsive img-thumbnail border" width="100%" |
||||
|
height="auto" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-12 my-3"> |
||||
|
<h4 class="mt-3" |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
||||
|
Stock Info Excel Report</h4> |
||||
|
<p |
||||
|
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
||||
|
Per warehouse you can get Available Qty, Virtual Qty, Incoming Qty, Outgoing Qty, Net On Hand Qty, |
||||
|
Total Sold & Total Purchased Qty. |
||||
|
You can get your stock valuation details too. |
||||
|
Negative stock will be highlighted in "red" cells. |
||||
|
</p> |
||||
|
<img src="assets/screenshots/stock_xlsx2.png" class="img-responsive img-thumbnail border" width="100%" |
||||
|
height="auto" /> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- SUGGESTED PRODUCTS --> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center" |
||||
|
style="text-align: center; padding: 2.5rem 1rem !important;"> |
||||
|
<h2 style="color: #212529 !important;">Suggested Products</h2> |
||||
|
<hr |
||||
|
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" /> |
||||
|
|
||||
|
<div id="demo1" class="row carousel slide" data-ride="carousel"> |
||||
|
<!-- The slideshow --> |
||||
|
<div class="carousel-inner"> |
||||
|
<div class="carousel-item active" style="min-height:0px"> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/14.0/export_stockinfo_xls/" target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-top-left-radius:10px; border-top-right-radius:10px" |
||||
|
src="./assets/modules/export_image.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/14.0/dashboard_pos/" target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-top-left-radius:10px; border-top-right-radius:10px" |
||||
|
src="./assets/modules/pos_image.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/14.0/product_approval_management/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-top-left-radius:10px; border-top-right-radius:10px" |
||||
|
src="./assets/modules/approval_image.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="carousel-item" style="min-height:0px"> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/14.0/base_account_budget/" target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-top-left-radius:10px; border-top-right-radius:10px" |
||||
|
src="./assets/modules/budget_image.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/14.0/shopify_odoo_connector/" target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-top-left-radius:10px; border-top-right-radius:10px" |
||||
|
src="./assets/modules/shopify_image.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/14.0/odoo11_magento2/" target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-top-left-radius:10px; border-top-right-radius:10px" |
||||
|
src="./assets/modules/magento_image.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Left and right controls --> |
||||
|
<a class="carousel-control-prev" href="#demo1" data-slide="prev" |
||||
|
style="left:-25px;width: 35px;color: #000;"> <span class="carousel-control-prev-icon"><i |
||||
|
class="fa fa-chevron-left" style="font-size:24px"></i></span> </a> <a |
||||
|
class="carousel-control-next" href="#demo1" data-slide="next" |
||||
|
style="right:-25px;width: 35px;color: #000;"> |
||||
|
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right" |
||||
|
style="font-size:24px"></i></span> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF SUGGESTED PRODUCTS --> |
||||
|
|
||||
|
<!-- OUR SERVICES --> |
||||
|
<section class="container" style="margin-top: 6rem !important;"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center"> |
||||
|
<h2 style="color: #212529 !important;">Our Services</h2> |
||||
|
<hr |
||||
|
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/cogs.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Customization</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/wrench.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Implementation</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/lifebuoy.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Support</h6> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/user.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Hire |
||||
|
Odoo |
||||
|
Developer</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/puzzle.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Integration</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/update.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Migration</h6> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/consultation.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Consultancy</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/training.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Implementation</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/license.png" class="img-responsive" height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Licensing Consultancy</h6> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- END OF END OF OUR SERVICES --> |
||||
|
|
||||
|
<!-- OUR INDUSTRIES --> |
||||
|
<section class="container" style="margin-top: 6rem !important;"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center"> |
||||
|
<h2 style="color: #212529 !important;">Our Industries</h2> |
||||
|
<hr |
||||
|
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/trading-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Trading |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Easily procure |
||||
|
and |
||||
|
sell your products</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/pos-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
POS |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Easy |
||||
|
configuration |
||||
|
and convivial experience</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/education-black.png" class="img-responsive mb-3" height="48px" |
||||
|
width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Education |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
A platform for |
||||
|
educational management</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/manufacturing-black.png" class="img-responsive mb-3" height="48px" |
||||
|
width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Manufacturing |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Plan, track and |
||||
|
schedule your operations</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/ecom-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
E-commerce & Website |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Mobile |
||||
|
friendly, |
||||
|
awe-inspiring product pages</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/service-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Service Management |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Keep track of |
||||
|
services and invoice</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/restaurant-black.png" class="img-responsive mb-3" height="48px" |
||||
|
width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Restaurant |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Run your bar or |
||||
|
restaurant methodically</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="./assets/icons/hotel-black.png" class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 |
||||
|
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Hotel Management |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
An |
||||
|
all-inclusive |
||||
|
hotel management application</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<!-- END OF END OF OUR INDUSTRIES --> |
||||
|
|
||||
|
<!-- FOOTER --> |
||||
|
<!-- Footer Section --> |
||||
|
<section class="container" style="margin: 5rem auto 2rem;"> |
||||
|
<div class="row" style="max-width:1540px;"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center"> |
||||
|
<h2 style="color: #212529 !important;">Need Help?</h2> |
||||
|
<hr |
||||
|
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Contact Cards --> |
||||
|
<div class="row d-flex justify-content-center align-items-center" |
||||
|
style="max-width:1540px; margin: 0 auto 2rem auto;"> |
||||
|
|
||||
|
<div class="col-lg-12" style="padding: 0rem 3rem 2rem; border-radius: 10px; margin-right: 3rem; "> |
||||
|
|
||||
|
<div class="row mt-4"> |
||||
|
<div class="col-lg-6"> |
||||
|
<a href="mailto:odoo@cybrosys.com" target="_blank" class="btn btn-block mb-2 deep_hover" |
||||
|
style="text-decoration: none; background-color: #4d4d4d; color: #FFF; border-radius: 4px;"><i |
||||
|
class="fa fa-envelope mr-2"></i>odoo@cybrosys.com</a> |
||||
|
</div> |
||||
|
<div class="col-lg-6"> |
||||
|
<a href="https://api.whatsapp.com/send?phone=918606827707" target="_blank" |
||||
|
class="btn btn-block mb-2 deep_hover" |
||||
|
style="text-decoration: none; background-color: #25D366; color: #FFF; border-radius: 4px;"><i |
||||
|
class="fa fa-whatsapp mr-2"></i>WhatsApp</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
<!-- End of Contact Cards --> |
||||
|
</section> |
||||
|
<!-- Footer --> |
||||
|
<section class="oe_container" style="padding: 2rem 3rem 1rem;"> |
||||
|
<div class="row" style="max-width:1540px; margin: 0 auto; margin-right: 3rem; "> |
||||
|
<!-- Logo --> |
||||
|
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin-top: 3rem;"> |
||||
|
<img src="https://www.cybrosys.com/images/logo.png" width="200px" height="auto" /> |
||||
|
</div> |
||||
|
<!-- End of Logo --> |
||||
|
<div class="col-lg-12"> |
||||
|
<hr |
||||
|
style="margin-top: 3rem;background: linear-gradient(90deg, rgba(2,0,36,0) 0%, rgba(229,229,229,1) 33%, rgba(229,229,229,1) 58%, rgba(0,212,255,0) 100%); height: 2px; border-style: none;"> |
||||
|
<!-- End of Footer Section --> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- END OF FOOTER --> |
||||
|
|
||||
|
</div> |
@ -0,0 +1,21 @@ |
|||||
|
/** @odoo-module */ |
||||
|
|
||||
|
import { registry } from "@web/core/registry"; |
||||
|
import { download } from "@web/core/network/download"; |
||||
|
import framework from 'web.framework'; |
||||
|
import session from 'web.session'; |
||||
|
|
||||
|
registry.category("ir.actions.report handlers").add("stock_xlsx", async (action) => { |
||||
|
if (action.report_type === 'stock_xlsx') { |
||||
|
framework.blockUI(); |
||||
|
var def = $.Deferred(); |
||||
|
session.get_file({ |
||||
|
url: '/xlsx_reports', |
||||
|
data: action.data, |
||||
|
success: def.resolve.bind(def), |
||||
|
error: (error) => this.call('crash_manager', 'rpc_error', error), |
||||
|
complete: framework.unblockUI, |
||||
|
}); |
||||
|
return def; |
||||
|
} |
||||
|
}); |
@ -0,0 +1,49 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<odoo> |
||||
|
<data> |
||||
|
<record model='ir.ui.view' id='wizard_form'> |
||||
|
<field name="name">wizard.stock.history.form</field> |
||||
|
<field name="model">wizard.stock.history</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Wizard"> |
||||
|
<group string="Warehouse"> |
||||
|
<field name="warehouse" widget="many2many_tags"/> |
||||
|
</group> |
||||
|
<notebook> |
||||
|
<page string="Category"> |
||||
|
<field name="category"> |
||||
|
<tree> |
||||
|
<field name="name"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</page> |
||||
|
</notebook> |
||||
|
<footer> |
||||
|
<button name="export_xls" type="object" default_focus="1" |
||||
|
string="Export Product with Stock Info" class="oe_highlight" |
||||
|
context="{'xls_export':1}" icon="fa-download"/> |
||||
|
<button string="Cancel" class="oe_link" special="cancel" /> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model='ir.actions.act_window' id='wizard_act'> |
||||
|
<field name="name">Export product stock in Excel</field> |
||||
|
<field name="res_model">wizard.stock.history</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="view_id" ref="wizard_form"/> |
||||
|
<field name="target">new</field> |
||||
|
</record> |
||||
|
|
||||
|
|
||||
|
<menuitem name="Current stock in Excel" |
||||
|
parent="stock.menu_warehouse_report" |
||||
|
id="export_excel" |
||||
|
action="wizard_act" |
||||
|
sequence="3"/> |
||||
|
</data> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,39 @@ |
|||||
|
Biometric Device Integration v15 |
||||
|
================================ |
||||
|
This Cybrosys's module integrates Odoo attendance with biometric device attendance. |
||||
|
|
||||
|
Features |
||||
|
======== |
||||
|
* Integrates biometric device(Face+Thumb) with HR attendance. |
||||
|
* Managing attendance automatically |
||||
|
* Keeps zk machine history in Odoo |
||||
|
* Option to configure multiple zk devices |
||||
|
* Option to clear all zk history from both device and Odoo |
||||
|
|
||||
|
Technical Notes |
||||
|
=============== |
||||
|
Used Libraries: |
||||
|
|
||||
|
*This integration is only applicable for the the device ZKteco model 'uFace 202' & 'iFace990' |
||||
|
* zklib |
||||
|
you can install zklib library using "sudo pip install zklib" |
||||
|
|
||||
|
Compatible Devices |
||||
|
|
||||
|
*ZKteco model 'uFace 202' |
||||
|
*ZKteco model 'iFace990' |
||||
|
|
||||
|
Author |
||||
|
======= |
||||
|
* Cybrosys Techno Solutions <https://www.cybrosys.com> |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
Developer: Niyas Raphy @ Cybrosys, odoo@cybrosys.com V11 |
||||
|
Developer: Jesni Banu @ cybrosys, odoo@cybrosys.com V10 |
||||
|
Developer: Basith @ Cybrosys, odoo@cybrosys.com V12 |
||||
|
Developer: Varsha Vivek @ Cybrosys, odoo@cybrosys.com V13 |
||||
|
Developer: Ijaz Ahammed @ Cybrosys, odoo@cybrosys.com V14 |
||||
|
Developer: Noushid Khan @ Cybrosys, odoo@cybrosys.com V15 |
||||
|
Developer: Mostafa Shokiel , mostafa.shokiel@gmail.com |
||||
|
|
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
from . import models |
@ -0,0 +1,43 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2021-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
{ |
||||
|
'name': 'Biometric Device Integration', |
||||
|
'version': '15.0.1.0.0', |
||||
|
'summary': """Integrating Biometric Device (Model: ZKteco uFace 202) With HR Attendance (Face + Thumb)""", |
||||
|
'description': """This module integrates Odoo with the biometric device(Model: ZKteco uFace 202),odoo15,odoo,hr,attendance""", |
||||
|
'category': 'Generic Modules/Human Resources', |
||||
|
'author': 'Cybrosys Techno Solutions, Mostafa Shokiel', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'website': "https://www.cybrosys.com", |
||||
|
'depends': ['base_setup', 'hr_attendance'], |
||||
|
'data': [ |
||||
|
'security/ir.model.access.csv', |
||||
|
'views/zk_machine_view.xml', |
||||
|
'views/zk_machine_attendance_view.xml', |
||||
|
'data/download_data.xml' |
||||
|
], |
||||
|
'images': ['static/description/banner.png'], |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo noupdate="1"> |
||||
|
<record forcecreate="True" id="cron_download_data" model="ir.cron"> |
||||
|
<field name="name">Download Data</field> |
||||
|
<field eval="True" name="active"/> |
||||
|
<field name="user_id" ref="base.user_admin"/> |
||||
|
<field name="interval_number">10</field> |
||||
|
<field name="interval_type">minutes</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="model_id" ref="hr_zk_attendance.model_zk_machine"/> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code">model.cron_download()</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,7 @@ |
|||||
|
## Module <hr_zk_attendance> |
||||
|
|
||||
|
#### 03.10.2021 |
||||
|
#### Version 15.0.1.0.0 |
||||
|
##### ADD |
||||
|
- Initial commit |
||||
|
|
@ -0,0 +1,25 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
from . import zk_machine |
||||
|
from . import machine_analysis |
||||
|
from . import zklib |
||||
|
|
@ -0,0 +1,106 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
from odoo import tools |
||||
|
from odoo import models, fields, api, _ |
||||
|
|
||||
|
|
||||
|
class HrEmployee(models.Model): |
||||
|
_inherit = 'hr.employee' |
||||
|
|
||||
|
device_id = fields.Char(string='Biometric Device ID') |
||||
|
|
||||
|
|
||||
|
class ZkMachine(models.Model): |
||||
|
_name = 'zk.machine.attendance' |
||||
|
_inherit = 'hr.attendance' |
||||
|
|
||||
|
@api.constrains('check_in', 'check_out', 'employee_id') |
||||
|
def _check_validity(self): |
||||
|
"""overriding the __check_validity function for employee attendance.""" |
||||
|
pass |
||||
|
|
||||
|
device_id = fields.Char(string='Biometric Device ID') |
||||
|
punch_type = fields.Selection([('0', 'Check In'), |
||||
|
('1', 'Check Out'), |
||||
|
('2', 'Break Out'), |
||||
|
('3', 'Break In'), |
||||
|
('4', 'Overtime In'), |
||||
|
('5', 'Overtime Out')], |
||||
|
string='Punching Type') |
||||
|
|
||||
|
attendance_type = fields.Selection([('1', 'Finger'), |
||||
|
('15', 'Face'), |
||||
|
('2','Type_2'), |
||||
|
('3','Password'), |
||||
|
('4','Card')], string='Category') |
||||
|
punching_time = fields.Datetime(string='Punching Time') |
||||
|
address_id = fields.Many2one('res.partner', string='Working Address') |
||||
|
|
||||
|
|
||||
|
class ReportZkDevice(models.Model): |
||||
|
_name = 'zk.report.daily.attendance' |
||||
|
_auto = False |
||||
|
_order = 'punching_day desc' |
||||
|
|
||||
|
name = fields.Many2one('hr.employee', string='Employee') |
||||
|
punching_day = fields.Datetime(string='Date') |
||||
|
address_id = fields.Many2one('res.partner', string='Working Address') |
||||
|
attendance_type = fields.Selection([('1', 'Finger'), |
||||
|
('15', 'Face'), |
||||
|
('2','Type_2'), |
||||
|
('3','Password'), |
||||
|
('4','Card')], |
||||
|
string='Category') |
||||
|
punch_type = fields.Selection([('0', 'Check In'), |
||||
|
('1', 'Check Out'), |
||||
|
('2', 'Break Out'), |
||||
|
('3', 'Break In'), |
||||
|
('4', 'Overtime In'), |
||||
|
('5', 'Overtime Out')], string='Punching Type') |
||||
|
punching_time = fields.Datetime(string='Punching Time') |
||||
|
|
||||
|
def init(self): |
||||
|
tools.drop_view_if_exists(self._cr, 'zk_report_daily_attendance') |
||||
|
query = """ |
||||
|
create or replace view zk_report_daily_attendance as ( |
||||
|
select |
||||
|
min(z.id) as id, |
||||
|
z.employee_id as name, |
||||
|
z.write_date as punching_day, |
||||
|
z.address_id as address_id, |
||||
|
z.attendance_type as attendance_type, |
||||
|
z.punching_time as punching_time, |
||||
|
z.punch_type as punch_type |
||||
|
from zk_machine_attendance z |
||||
|
join hr_employee e on (z.employee_id=e.id) |
||||
|
GROUP BY |
||||
|
z.employee_id, |
||||
|
z.write_date, |
||||
|
z.address_id, |
||||
|
z.attendance_type, |
||||
|
z.punch_type, |
||||
|
z.punching_time |
||||
|
) |
||||
|
""" |
||||
|
self._cr.execute(query) |
||||
|
|
||||
|
|
@ -0,0 +1,208 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2020-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
import pytz |
||||
|
import sys |
||||
|
import datetime |
||||
|
import logging |
||||
|
import binascii |
||||
|
|
||||
|
from . import zklib |
||||
|
from .zkconst import * |
||||
|
from struct import unpack |
||||
|
from odoo import api, fields, models |
||||
|
from odoo import _ |
||||
|
from odoo.exceptions import UserError, ValidationError |
||||
|
_logger = logging.getLogger(__name__) |
||||
|
try: |
||||
|
from zk import ZK, const |
||||
|
except ImportError: |
||||
|
_logger.error("Please Install pyzk library.") |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class HrAttendance(models.Model): |
||||
|
_inherit = 'hr.attendance' |
||||
|
|
||||
|
device_id = fields.Char(string='Biometric Device ID') |
||||
|
|
||||
|
|
||||
|
class ZkMachine(models.Model): |
||||
|
_name = 'zk.machine' |
||||
|
|
||||
|
name = fields.Char(string='Machine IP', required=True) |
||||
|
port_no = fields.Integer(string='Port No', required=True) |
||||
|
address_id = fields.Many2one('res.partner', string='Working Address') |
||||
|
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id.id) |
||||
|
|
||||
|
def device_connect(self, zk): |
||||
|
try: |
||||
|
conn = zk.connect() |
||||
|
return conn |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
def clear_attendance(self): |
||||
|
for info in self: |
||||
|
try: |
||||
|
machine_ip = info.name |
||||
|
zk_port = info.port_no |
||||
|
timeout = 30 |
||||
|
try: |
||||
|
zk = ZK(machine_ip, port=zk_port, timeout=timeout, password=0, force_udp=False, ommit_ping=False) |
||||
|
except NameError: |
||||
|
raise UserError(_("Please install it with 'pip3 install pyzk'.")) |
||||
|
conn = self.device_connect(zk) |
||||
|
if conn: |
||||
|
conn.enable_device() |
||||
|
clear_data = zk.get_attendance() |
||||
|
if clear_data: |
||||
|
# conn.clear_attendance() |
||||
|
self._cr.execute("""delete from zk_machine_attendance""") |
||||
|
conn.disconnect() |
||||
|
raise UserError(_('Attendance Records Deleted.')) |
||||
|
else: |
||||
|
raise UserError(_('Unable to clear Attendance log. Are you sure attendance log is not empty.')) |
||||
|
else: |
||||
|
raise UserError( |
||||
|
_('Unable to connect to Attendance Device. Please use Test Connection button to verify.')) |
||||
|
except: |
||||
|
raise ValidationError( |
||||
|
'Unable to clear Attendance log. Are you sure attendance device is connected & record is not empty.') |
||||
|
|
||||
|
def getSizeUser(self, zk): |
||||
|
"""Checks a returned packet to see if it returned CMD_PREPARE_DATA, |
||||
|
indicating that data packets are to be sent |
||||
|
|
||||
|
Returns the amount of bytes that are going to be sent""" |
||||
|
command = unpack('HHHH', zk.data_recv[:8])[0] |
||||
|
if command == CMD_PREPARE_DATA: |
||||
|
size = unpack('I', zk.data_recv[8:12])[0] |
||||
|
print("size", size) |
||||
|
return size |
||||
|
else: |
||||
|
return False |
||||
|
|
||||
|
def zkgetuser(self, zk): |
||||
|
"""Start a connection with the time clock""" |
||||
|
try: |
||||
|
users = zk.get_users() |
||||
|
print(users) |
||||
|
return users |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
@api.model |
||||
|
def cron_download(self): |
||||
|
machines = self.env['zk.machine'].search([]) |
||||
|
for machine in machines : |
||||
|
machine.download_attendance() |
||||
|
|
||||
|
def download_attendance(self): |
||||
|
_logger.info("++++++++++++Cron Executed++++++++++++++++++++++") |
||||
|
zk_attendance = self.env['zk.machine.attendance'] |
||||
|
att_obj = self.env['hr.attendance'] |
||||
|
for info in self: |
||||
|
machine_ip = info.name |
||||
|
zk_port = info.port_no |
||||
|
timeout = 15 |
||||
|
try: |
||||
|
zk = ZK(machine_ip, port=zk_port, timeout=timeout, password=0, force_udp=False, ommit_ping=False) |
||||
|
except NameError: |
||||
|
raise UserError(_("Pyzk module not Found. Please install it with 'pip3 install pyzk'.")) |
||||
|
conn = self.device_connect(zk) |
||||
|
if conn: |
||||
|
# conn.disable_device() #Device Cannot be used during this time. |
||||
|
try: |
||||
|
user = conn.get_users() |
||||
|
except: |
||||
|
user = False |
||||
|
try: |
||||
|
attendance = conn.get_attendance() |
||||
|
except: |
||||
|
attendance = False |
||||
|
if attendance: |
||||
|
for each in attendance: |
||||
|
atten_time = each.timestamp |
||||
|
atten_time = datetime.strptime(atten_time.strftime('%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') |
||||
|
local_tz = pytz.timezone( |
||||
|
self.env.user.partner_id.tz or 'GMT') |
||||
|
local_dt = local_tz.localize(atten_time, is_dst=None) |
||||
|
utc_dt = local_dt.astimezone(pytz.utc) |
||||
|
utc_dt = utc_dt.strftime("%Y-%m-%d %H:%M:%S") |
||||
|
atten_time = datetime.strptime( |
||||
|
utc_dt, "%Y-%m-%d %H:%M:%S") |
||||
|
atten_time = fields.Datetime.to_string(atten_time) |
||||
|
if user: |
||||
|
for uid in user: |
||||
|
if uid.user_id == each.user_id: |
||||
|
get_user_id = self.env['hr.employee'].search( |
||||
|
[('device_id', '=', each.user_id)]) |
||||
|
if get_user_id: |
||||
|
duplicate_atten_ids = zk_attendance.search( |
||||
|
[('device_id', '=', each.user_id), ('punching_time', '=', atten_time)]) |
||||
|
if duplicate_atten_ids: |
||||
|
continue |
||||
|
else: |
||||
|
zk_attendance.create({'employee_id': get_user_id.id, |
||||
|
'device_id': each.user_id, |
||||
|
'attendance_type': str(each.status), |
||||
|
'punch_type': str(each.punch), |
||||
|
'punching_time': atten_time, |
||||
|
'address_id': info.address_id.id}) |
||||
|
att_var = att_obj.search([('employee_id', '=', get_user_id.id), |
||||
|
('check_out', '=', False)]) |
||||
|
print('ddfcd', str(each.status)) |
||||
|
if each.punch == 0: #check-in |
||||
|
if not att_var: |
||||
|
att_obj.create({'employee_id': get_user_id.id, |
||||
|
'check_in': atten_time}) |
||||
|
if each.punch == 1: #check-out |
||||
|
if len(att_var) == 1: |
||||
|
att_var.write({'check_out': atten_time}) |
||||
|
else: |
||||
|
att_var1 = att_obj.search([('employee_id', '=', get_user_id.id)]) |
||||
|
if att_var1: |
||||
|
att_var1[-1].write({'check_out': atten_time}) |
||||
|
|
||||
|
else: |
||||
|
print('ddfcd', str(each.status)) |
||||
|
print('user', uid.name) |
||||
|
employee = self.env['hr.employee'].create( |
||||
|
{'device_id': each.user_id, 'name': uid.name}) |
||||
|
zk_attendance.create({'employee_id': employee.id, |
||||
|
'device_id': each.user_id, |
||||
|
'attendance_type': str(each.status), |
||||
|
'punch_type': str(each.punch), |
||||
|
'punching_time': atten_time, |
||||
|
'address_id': info.address_id.id}) |
||||
|
att_obj.create({'employee_id': employee.id, |
||||
|
'check_in': atten_time}) |
||||
|
else: |
||||
|
pass |
||||
|
# zk.enableDevice() |
||||
|
conn.disconnect |
||||
|
return True |
||||
|
else: |
||||
|
raise UserError(_('Unable to get the attendance log, please try again later.')) |
||||
|
else: |
||||
|
raise UserError(_('Unable to connect, please check the parameters and network connections.')) |
@ -0,0 +1,118 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
import binascii |
||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def getSizeAttendance(self): |
||||
|
"""Checks a returned packet to see if it returned CMD_PREPARE_DATA, |
||||
|
indicating that data packets are to be sent |
||||
|
|
||||
|
Returns the amount of bytes that are going to be sent""" |
||||
|
command = unpack('HHHH', self.data_recv[:8])[0] |
||||
|
if command == CMD_PREPARE_DATA: |
||||
|
size = unpack('I', self.data_recv[8:12])[0] |
||||
|
return size |
||||
|
else: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def reverseHex(hexstr): |
||||
|
tmp = '' |
||||
|
for i in reversed(range(int(len(hexstr)/2))): |
||||
|
tmp += hexstr[i*2:(i*2)+2] |
||||
|
|
||||
|
return tmp |
||||
|
|
||||
|
def zkgetattendance(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_ATTLOG_RRQ |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
|
||||
|
if getSizeAttendance(self): |
||||
|
bytes = getSizeAttendance(self) |
||||
|
while bytes > 0: |
||||
|
data_recv, addr = self.zkclient.recvfrom(1032) |
||||
|
self.attendancedata.append(data_recv) |
||||
|
bytes -= 1024 |
||||
|
|
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
data_recv = self.zkclient.recvfrom(8) |
||||
|
|
||||
|
attendance = [] |
||||
|
if len(self.attendancedata) > 0: |
||||
|
# The first 4 bytes don't seem to be related to the user |
||||
|
for x in range(len(self.attendancedata)): |
||||
|
if x > 0: |
||||
|
self.attendancedata[x] = self.attendancedata[x][8:] |
||||
|
|
||||
|
attendancedata = b''.join( self.attendancedata ) |
||||
|
|
||||
|
attendancedata = attendancedata[14:] |
||||
|
|
||||
|
while len(attendancedata) > 40: |
||||
|
|
||||
|
uid, state, timestamp, space = unpack( '24s1s4s11s', attendancedata.ljust(40)[:40] ) |
||||
|
|
||||
|
|
||||
|
# Clean up some messy characters from the user name |
||||
|
#uid = unicode(uid.strip('\x00|\x01\x10x'), errors='ignore') |
||||
|
uid = uid.split(b'\x00', 1)[0].decode('utf-8') |
||||
|
#print "%s, %s, %s" % (uid, state, decode_time( int( reverseHex( timestamp.encode('hex') ), 16 ) ) ) |
||||
|
|
||||
|
attendance.append( ( uid, int( binascii.hexlify(state), 16 ), decode_time( int( reverseHex( binascii.hexlify(timestamp).decode('utf-8')), 16 ) ) ) ) |
||||
|
|
||||
|
attendancedata = attendancedata[40:] |
||||
|
|
||||
|
return attendance |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkclearattendance(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_CLEAR_ATTLOG |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
@ -0,0 +1,63 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkconnect(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_CONNECT |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = 0 |
||||
|
reply_id = -1 + USHRT_MAX |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
|
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
|
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
|
||||
|
return self.checkValid( self.data_recv ) |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkdisconnect(self): |
||||
|
"""Disconnect from the clock""" |
||||
|
command = CMD_EXIT |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
|
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
|
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
|
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
return self.checkValid( self.data_recv ) |
@ -0,0 +1,71 @@ |
|||||
|
from datetime import datetime, date |
||||
|
|
||||
|
USHRT_MAX = 65535 |
||||
|
|
||||
|
|
||||
|
CMD_CONNECT = 1000 |
||||
|
CMD_EXIT = 1001 |
||||
|
CMD_ENABLEDEVICE = 1002 |
||||
|
CMD_DISABLEDEVICE = 1003 |
||||
|
|
||||
|
CMD_ACK_OK = 2000 |
||||
|
CMD_ACK_ERROR = 2001 |
||||
|
CMD_ACK_DATA = 2002 |
||||
|
|
||||
|
CMD_PREPARE_DATA = 1500 |
||||
|
CMD_DATA = 1501 |
||||
|
|
||||
|
CMD_USERTEMP_RRQ = 9 |
||||
|
CMD_ATTLOG_RRQ = 13 |
||||
|
CMD_CLEAR_DATA = 14 |
||||
|
CMD_CLEAR_ATTLOG = 15 |
||||
|
|
||||
|
CMD_WRITE_LCD = 66 |
||||
|
|
||||
|
CMD_GET_TIME = 201 |
||||
|
CMD_SET_TIME = 202 |
||||
|
|
||||
|
CMD_VERSION = 1100 |
||||
|
CMD_DEVICE = 11 |
||||
|
|
||||
|
CMD_CLEAR_ADMIN = 20 |
||||
|
CMD_SET_USER = 8 |
||||
|
|
||||
|
LEVEL_USER = 0 |
||||
|
LEVEL_ADMIN = 14 |
||||
|
|
||||
|
def encode_time(t): |
||||
|
"""Encode a timestamp send at the timeclock |
||||
|
|
||||
|
copied from zkemsdk.c - EncodeTime""" |
||||
|
d = ( (t.year % 100) * 12 * 31 + ((t.month - 1) * 31) + t.day - 1) *\ |
||||
|
(24 * 60 * 60) + (t.hour * 60 + t.minute) * 60 + t.second |
||||
|
|
||||
|
return d |
||||
|
|
||||
|
|
||||
|
def decode_time(t): |
||||
|
"""Decode a timestamp retrieved from the timeclock |
||||
|
|
||||
|
copied from zkemsdk.c - DecodeTime""" |
||||
|
second = t % 60 |
||||
|
t = t / 60 |
||||
|
|
||||
|
minute = t % 60 |
||||
|
t = t / 60 |
||||
|
|
||||
|
hour = t % 24 |
||||
|
t = t / 24 |
||||
|
|
||||
|
day = t % 31+1 |
||||
|
t = t / 31 |
||||
|
|
||||
|
month = t % 12+1 |
||||
|
t = t / 12 |
||||
|
|
||||
|
year = t + 2000 |
||||
|
|
||||
|
d = datetime(int(year), int(month), int(day), int(hour), int(minute), int(second)) |
||||
|
|
||||
|
return d |
||||
|
|
@ -0,0 +1,82 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkdevicename(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~DeviceName' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkenabledevice(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_ENABLEDEVICE |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
def zkdisabledevice(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DISABLEDEVICE |
||||
|
command_string = '\x00\x00' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
@ -0,0 +1,56 @@ |
|||||
|
def zkextendfmt(self): |
||||
|
try: |
||||
|
test = self.exttrynumber |
||||
|
except: |
||||
|
self.exttrynumber = 1 |
||||
|
|
||||
|
data_seq=[ self.data_recv.encode("hex")[4:6], self.data_recv.encode("hex")[6:8] ] |
||||
|
#print data_seq |
||||
|
if self.exttrynumber == 1: |
||||
|
plus1 = 0 |
||||
|
plus2 = 0 |
||||
|
else: |
||||
|
plus1 = -1 |
||||
|
plus2 = +1 |
||||
|
|
||||
|
|
||||
|
desc = ": +"+hex( int('99', 16)+plus1 ).lstrip('0x')+", +"+hex(int('b1', 16)+plus2).lstrip("0x") |
||||
|
self.data_seq1 = hex( int( data_seq[0], 16 ) + int( '99', 16 ) + plus1 ).lstrip("0x") |
||||
|
self.data_seq2 = hex( int( data_seq[1], 16 ) + int( 'b1', 16 ) + plus2 ).lstrip("0x") |
||||
|
|
||||
|
if len(self.data_seq1) >= 3: |
||||
|
#self.data_seq2 = hex( int( self.data_seq2, 16 ) + int( self.data_seq1[:1], 16) ).lstrip("0x") |
||||
|
self.data_seq1 = self.data_seq1[-2:] |
||||
|
|
||||
|
if len(self.data_seq2) >= 3: |
||||
|
#self.data_seq1 = hex( int( self.data_seq1, 16 ) + int( self.data_seq2[:1], 16) ).lstrip("0x") |
||||
|
self.data_seq2 = self.data_seq2[-2:] |
||||
|
|
||||
|
|
||||
|
if len(self.data_seq1) <= 1: |
||||
|
self.data_seq1 = "0"+self.data_seq1 |
||||
|
|
||||
|
if len(self.data_seq2) <= 1: |
||||
|
self.data_seq2 = "0"+self.data_seq2 |
||||
|
|
||||
|
|
||||
|
counter = hex( self.counter ).lstrip("0x") |
||||
|
if len(counter): |
||||
|
counter = "0" + counter |
||||
|
#print self.data_seq1+" "+self.data_seq2+desc |
||||
|
data = "0b00"+self.data_seq1+self.data_seq2+self.id_com+counter+"007e457874656e64466d7400" |
||||
|
self.zkclient.sendto(data.decode("hex"), self.address) |
||||
|
#print data |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
except: |
||||
|
if self.exttrynumber == 1: |
||||
|
self.exttrynumber = 2 |
||||
|
tmp = zkextendfmt(self) |
||||
|
if len(tmp) < 1: |
||||
|
self.exttrynumber = 1 |
||||
|
|
||||
|
self.id_com = self.data_recv.encode("hex")[8:12] |
||||
|
self.counter = self.counter+1 |
||||
|
#print self.data_recv.encode("hex") |
||||
|
return self.data_recv[8:] |
@ -0,0 +1,61 @@ |
|||||
|
def zkextendoplog(self, index=0): |
||||
|
try: |
||||
|
test = self.extlogtrynumber |
||||
|
except: |
||||
|
self.extlogtrynumber = 1 |
||||
|
|
||||
|
data_seq = [ self.data_recv.encode("hex")[4:6], self.data_recv.encode("hex")[6:8] ] |
||||
|
|
||||
|
if index==0: |
||||
|
self.data_seq1 = hex( int( data_seq[0], 16 ) + int( '104', 16 ) ).lstrip("0x") |
||||
|
self.data_seq2 = hex( int( data_seq[1], 16 ) + int( '19', 16 ) ).lstrip("0x") |
||||
|
desc = ": +104, +19" |
||||
|
header="0b00" |
||||
|
elif index==1: |
||||
|
self.data_seq1 = hex( abs( int( data_seq[0], 16 ) - int( '2c', 16 ) ) ).lstrip("0x") |
||||
|
self.data_seq2 = hex( abs( int( data_seq[1], 16 ) - int( '2', 16 ) ) ).lstrip("0x") |
||||
|
desc = ": -2c, -2" |
||||
|
header="d107" |
||||
|
elif index>=2: |
||||
|
self.data_seq1 = hex( abs( int( data_seq[0], 16 ) - int( '2c', 16 ) ) ).lstrip("0x") |
||||
|
self.data_seq2 = hex( abs( int( data_seq[1], 16 ) - int( '2', 16 ) ) ).lstrip("0x") |
||||
|
desc = ": -2c, -2" |
||||
|
header="ffff" |
||||
|
|
||||
|
|
||||
|
#print self.data_seq1+" "+self.data_seq2 |
||||
|
if len(self.data_seq1) >= 3: |
||||
|
self.data_seq2 = hex( int( self.data_seq2, 16 ) + int( self.data_seq1[:1], 16) ).lstrip("0x") |
||||
|
self.data_seq1 = self.data_seq1[-2:] |
||||
|
|
||||
|
if len(self.data_seq2) >= 3: |
||||
|
self.data_seq1 = hex( int( self.data_seq1, 16 ) + int( self.data_seq2[:1], 16) ).lstrip("0x") |
||||
|
self.data_seq2 = self.data_seq2[-2:] |
||||
|
|
||||
|
if len(self.data_seq1) <= 1: |
||||
|
self.data_seq1 = "0"+self.data_seq1 |
||||
|
|
||||
|
if len(self.data_seq2) <= 1: |
||||
|
self.data_seq2 = "0"+self.data_seq2 |
||||
|
|
||||
|
|
||||
|
counter = hex( self.counter ).lstrip("0x") |
||||
|
if len(counter): |
||||
|
counter = "0" + counter |
||||
|
|
||||
|
#print self.data_seq1+" "+self.data_seq2+desc |
||||
|
data = header+self.data_seq1+self.data_seq2+self.id_com+counter+"00457874656e644f504c6f6700" |
||||
|
self.zkclient.sendto(data.decode("hex"), self.address) |
||||
|
#print data |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
except: |
||||
|
bingung=1 |
||||
|
if self.extlogtrynumber == 1: |
||||
|
self.extlogtrynumber = 2 |
||||
|
zkextendoplog(self) |
||||
|
|
||||
|
self.id_com = self.data_recv.encode("hex")[8:12] |
||||
|
self.counter = self.counter+1 |
||||
|
#print self.data_recv.encode("hex") |
||||
|
return self.data_recv[8:] |
@ -0,0 +1,44 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
################################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# Copyright (C) 2018-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). |
||||
|
# Author: cybrosys(<https://www.cybrosys.com>) |
||||
|
# |
||||
|
# This program is free software: you can modify |
||||
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
################################################################################### |
||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkfaceon(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = 'FaceFunOn' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
@ -0,0 +1,156 @@ |
|||||
|
from socket import * |
||||
|
from .zkconnect import * |
||||
|
from .zkversion import * |
||||
|
from .zkos import * |
||||
|
from .zkextendfmt import * |
||||
|
from .zkextendoplog import * |
||||
|
from .zkplatform import * |
||||
|
from .zkworkcode import * |
||||
|
from .zkssr import * |
||||
|
from .zkpin import * |
||||
|
from .zkface import * |
||||
|
from .zkserialnumber import * |
||||
|
from .zkdevice import * |
||||
|
from .zkuser import * |
||||
|
from .zkattendance import * |
||||
|
from .zktime import * |
||||
|
|
||||
|
class ZKLib: |
||||
|
|
||||
|
def __init__(self, ip, port): |
||||
|
self.address = (ip, port) |
||||
|
self.zkclient = socket(AF_INET, SOCK_DGRAM) |
||||
|
self.zkclient.settimeout(3) |
||||
|
self.session_id = 0 |
||||
|
self.userdata = [] |
||||
|
self.attendancedata = [] |
||||
|
|
||||
|
|
||||
|
def createChkSum(self, p): |
||||
|
"""This function calculates the chksum of the packet to be sent to the |
||||
|
time clock |
||||
|
|
||||
|
Copied from zkemsdk.c""" |
||||
|
l = len(p) |
||||
|
chksum = 0 |
||||
|
while l > 1: |
||||
|
chksum += unpack('H', pack('BB', p[0], p[1]))[0] |
||||
|
|
||||
|
p = p[2:] |
||||
|
if chksum > USHRT_MAX: |
||||
|
chksum -= USHRT_MAX |
||||
|
l -= 2 |
||||
|
|
||||
|
|
||||
|
if l: |
||||
|
chksum = chksum + p[-1] |
||||
|
|
||||
|
while chksum > USHRT_MAX: |
||||
|
chksum -= USHRT_MAX |
||||
|
|
||||
|
chksum = ~chksum |
||||
|
|
||||
|
while chksum < 0: |
||||
|
chksum += USHRT_MAX |
||||
|
|
||||
|
return pack('H', chksum) |
||||
|
|
||||
|
|
||||
|
def createHeader(self, command, chksum, session_id, reply_id, |
||||
|
command_string): |
||||
|
"""This function puts a the parts that make up a packet together and |
||||
|
packs them into a byte string""" |
||||
|
buf = pack('HHHH', command, chksum, session_id, reply_id) + command_string.encode(encoding='utf_8', errors='strict') |
||||
|
|
||||
|
buf = unpack('8B'+'%sB' % len(command_string), buf) |
||||
|
|
||||
|
chksum = unpack('H', self.createChkSum(buf))[0] |
||||
|
#print unpack('H', self.createChkSum(buf)) |
||||
|
reply_id += 1 |
||||
|
if reply_id >= USHRT_MAX: |
||||
|
reply_id -= USHRT_MAX |
||||
|
|
||||
|
buf = pack('HHHH', command, chksum, session_id, reply_id) |
||||
|
return buf + command_string.encode(encoding='utf_8', errors='strict') |
||||
|
|
||||
|
|
||||
|
def checkValid(self, reply): |
||||
|
"""Checks a returned packet to see if it returned CMD_ACK_OK, |
||||
|
indicating success""" |
||||
|
command = unpack('HHHH', reply[:8])[0] |
||||
|
if command == CMD_ACK_OK: |
||||
|
return True |
||||
|
else: |
||||
|
return False |
||||
|
|
||||
|
def connect(self): |
||||
|
return zkconnect(self) |
||||
|
|
||||
|
def disconnect(self): |
||||
|
return zkdisconnect(self) |
||||
|
|
||||
|
def version(self): |
||||
|
return zkversion(self) |
||||
|
|
||||
|
def osversion(self): |
||||
|
return zkos(self) |
||||
|
|
||||
|
def extendFormat(self): |
||||
|
return zkextendfmt(self) |
||||
|
|
||||
|
def extendOPLog(self, index=0): |
||||
|
return zkextendoplog(self, index) |
||||
|
|
||||
|
def platform(self): |
||||
|
return zkplatform(self) |
||||
|
|
||||
|
def fmVersion(self): |
||||
|
return zkplatformVersion(self) |
||||
|
|
||||
|
def workCode(self): |
||||
|
return zkworkcode(self) |
||||
|
|
||||
|
def ssr(self): |
||||
|
return zkssr(self) |
||||
|
|
||||
|
def pinWidth(self): |
||||
|
return zkpinwidth(self) |
||||
|
|
||||
|
def faceFunctionOn(self): |
||||
|
return zkfaceon(self) |
||||
|
|
||||
|
def serialNumber(self): |
||||
|
return zkserialnumber(self) |
||||
|
|
||||
|
def deviceName(self): |
||||
|
return zkdevicename(self) |
||||
|
|
||||
|
def disableDevice(self): |
||||
|
return zkdisabledevice(self) |
||||
|
|
||||
|
def enableDevice(self): |
||||
|
return zkenabledevice(self) |
||||
|
|
||||
|
def getUser(self): |
||||
|
return zkgetuser(self) |
||||
|
|
||||
|
def setUser(self, uid, userid, name, password, role): |
||||
|
return zksetuser(self, uid, userid, name, password, role) |
||||
|
|
||||
|
def clearUser(self): |
||||
|
return zkclearuser(self) |
||||
|
|
||||
|
def clearAdmin(self): |
||||
|
return zkclearadmin(self) |
||||
|
|
||||
|
def getAttendance(self): |
||||
|
return zkgetattendance(self) |
||||
|
|
||||
|
def clearAttendance(self): |
||||
|
return zkclearattendance(self) |
||||
|
|
||||
|
def setTime(self, t): |
||||
|
return zksettime(self, t) |
||||
|
|
||||
|
def getTime(self): |
||||
|
return zkgettime(self) |
@ -0,0 +1,23 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkos(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~OS' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
|
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
|
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
@ -0,0 +1,23 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkpinwidth(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~PIN2Width' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
@ -0,0 +1,43 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkplatform(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~Platform' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkplatformVersion(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~ZKFPVersion' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
@ -0,0 +1,23 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkserialnumber(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~SerialNumber' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
@ -0,0 +1,23 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkssr(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = '~SSR' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
@ -0,0 +1,50 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def reverseHex(hexstr): |
||||
|
tmp = '' |
||||
|
for i in reversed(range(len(hexstr)/2)): |
||||
|
tmp += hexstr[i*2:(i*2)+2] |
||||
|
|
||||
|
return tmp |
||||
|
|
||||
|
|
||||
|
def zksettime(self, t): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_SET_TIME |
||||
|
command_string = pack('I',encode_time(t)) |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkgettime(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_GET_TIME |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return decode_time( int( reverseHex( self.data_recv[8:].encode("hex") ), 16 ) ) |
||||
|
except: |
||||
|
return False |
@ -0,0 +1,140 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def getSizeUser(self): |
||||
|
"""Checks a returned packet to see if it returned CMD_PREPARE_DATA, |
||||
|
indicating that data packets are to be sent |
||||
|
|
||||
|
Returns the amount of bytes that are going to be sent""" |
||||
|
command = unpack('HHHH', self.data_recv[:8])[0] |
||||
|
if command == CMD_PREPARE_DATA: |
||||
|
size = unpack('I', self.data_recv[8:12])[0] |
||||
|
return size |
||||
|
else: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zksetuser(self, uid, userid, name, password, role): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_SET_USER |
||||
|
command_string = pack('sxs8s28ss7sx8s16s', chr( uid ), chr(role), password, name, chr(1), '', userid, '' ) |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkgetuser(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_USERTEMP_RRQ |
||||
|
command_string = '\x05' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
|
||||
|
|
||||
|
if getSizeUser(self): |
||||
|
bytes = getSizeUser(self) |
||||
|
|
||||
|
while bytes > 0: |
||||
|
data_recv, addr = self.zkclient.recvfrom(1032) |
||||
|
self.userdata.append(data_recv) |
||||
|
bytes -= 1024 |
||||
|
|
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
data_recv = self.zkclient.recvfrom(8) |
||||
|
|
||||
|
users = {} |
||||
|
if len(self.userdata) > 0: |
||||
|
# The first 4 bytes don't seem to be related to the user |
||||
|
for x in range(len(self.userdata)): |
||||
|
if x > 0: |
||||
|
self.userdata[x] = self.userdata[x][8:] |
||||
|
|
||||
|
userdata = ''.join( self.userdata ) |
||||
|
|
||||
|
userdata = userdata[11:] |
||||
|
|
||||
|
while len(userdata) > 72: |
||||
|
|
||||
|
uid, role, password, name, userid = unpack( '2s2s8s28sx31s', userdata.ljust(72)[:72] ) |
||||
|
|
||||
|
uid = int( uid.encode("hex"), 16) |
||||
|
# Clean up some messy characters from the user name |
||||
|
password = password.split('\x00', 1)[0] |
||||
|
password = unicode(password.strip('\x00|\x01\x10x'), errors='ignore') |
||||
|
|
||||
|
#uid = uid.split('\x00', 1)[0] |
||||
|
userid = unicode(userid.strip('\x00|\x01\x10x'), errors='ignore') |
||||
|
|
||||
|
name = name.split('\x00', 1)[0] |
||||
|
|
||||
|
if name.strip() == "": |
||||
|
name = uid |
||||
|
|
||||
|
users[uid] = (userid, name, int( role.encode("hex"), 16 ), password) |
||||
|
|
||||
|
#print("%d, %s, %s, %s, %s" % (uid, userid, name, int( role.encode("hex"), 16 ), password)) |
||||
|
userdata = userdata[72:] |
||||
|
|
||||
|
return users |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkclearuser(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_CLEAR_DATA |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def zkclearadmin(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_CLEAR_ADMIN |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
@ -0,0 +1,23 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkversion(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_VERSION |
||||
|
command_string = '' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
@ -0,0 +1,23 @@ |
|||||
|
from struct import pack, unpack |
||||
|
from .zkconst import * |
||||
|
|
||||
|
|
||||
|
def zkworkcode(self): |
||||
|
"""Start a connection with the time clock""" |
||||
|
command = CMD_DEVICE |
||||
|
command_string = 'WorkCode' |
||||
|
chksum = 0 |
||||
|
session_id = self.session_id |
||||
|
reply_id = unpack('HHHH', self.data_recv[:8])[3] |
||||
|
|
||||
|
buf = self.createHeader(command, chksum, session_id, |
||||
|
reply_id, command_string) |
||||
|
self.zkclient.sendto(buf, self.address) |
||||
|
#print buf.encode("hex") |
||||
|
try: |
||||
|
self.data_recv, addr = self.zkclient.recvfrom(1024) |
||||
|
self.session_id = unpack('HHHH', self.data_recv[:8])[2] |
||||
|
return self.data_recv[8:] |
||||
|
except: |
||||
|
return False |
||||
|
|
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 204 KiB |
After Width: | Height: | Size: 350 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 44 KiB |