diff --git a/__init__.py b/__init__.py index 3da5661..b7684b6 100644 --- a/__init__.py +++ b/__init__.py @@ -27,9 +27,10 @@ def register(): sale.OrderStatusTime, shop.SaleShop, bom.BOM, - production.WorkStation, - production.WorkStationPrinter, + # production.WorkStation, + # production.WorkStationPrinter, production.ConfigurationTask, + production.TaskPrinter, production.Task, product.ProductMixOption, product.Product, diff --git a/bom.py b/bom.py index d0bcb8d..232527c 100644 --- a/bom.py +++ b/bom.py @@ -12,8 +12,6 @@ def round_dec(number): class BOM(metaclass=PoolMeta): __name__ = 'production.bom' - tasks_configuration = fields.One2Many('production.configuration_task', 'ldm', 'Tasks Configuration') - @classmethod def copy(cls, records, default=None): if default is None: @@ -21,5 +19,4 @@ class BOM(metaclass=PoolMeta): else: default = default.copy() default.setdefault('output_products', None) - default.setdefault('tasks_configuration', None) return super(BOM, cls).copy(records, default=default) diff --git a/product.py b/product.py index 2e386f1..919abc7 100644 --- a/product.py +++ b/product.py @@ -5,6 +5,7 @@ from trytond.pool import PoolMeta from .exceptions import ProductMixRequiredError from trytond.i18n import gettext + class Product(metaclass=PoolMeta): __name__ = 'product.product' products_mix = fields.Many2Many('product.product-mix.option', @@ -17,8 +18,8 @@ class Product(metaclass=PoolMeta): if self.products_mix and self.quantity_mix_required and len(self.products_mix) < self.quantity_mix_required: raise ProductMixRequiredError( gettext( - "sale_pos_frontend_rest.msg_quantity_mix_error", - product_mix=len(self.products_mix), + "sale_pos_frontend_rest.msg_quantity_mix_error", + product_mix=len(self.products_mix), quantity=self.quantity_mix_required)) @classmethod diff --git a/production.py b/production.py index 0cedbbd..b8dfc3b 100644 --- a/production.py +++ b/production.py @@ -4,10 +4,11 @@ from trytond.model import fields, ModelSQL, ModelView from trytond.transaction import Transaction from decimal import Decimal -from trytond.pool import PoolMeta, Pool +from datetime import datetime, timedelta +from trytond.pool import Pool from trytond.pyson import Eval -from trytond.exceptions import UserError -from trytond.i18n import gettext +# from trytond.exceptions import UserError +# from trytond.i18n import gettext def round_dec(number): @@ -16,21 +17,29 @@ def round_dec(number): STATES = {'required': True} +# for remove +# class WorkStation(ModelSQL, ModelView): +# "Work Station" +# __name__ = 'production.workstation' -class WorkStation(ModelSQL, ModelView): - "Work Station" - __name__ = 'production.workstation' +# name = fields.Char('Name', states=STATES) +# code = fields.Char('Code', states=STATES) +# printers = fields.Many2Many('production.workstation.pos_printer', 'work_station', 'printer', 'Printers') - name = fields.Char('Name', states=STATES) - code = fields.Char('Code', states=STATES) - printers = fields.Many2Many('production.workstation.pos_printer', 'work_station', 'printer', 'Printers') +# for remove +# class WorkStationPrinter(ModelSQL, ModelView): +# "Work Station" +# __name__ = 'production.workstation.pos_printer' + +# work_station = fields.Many2One('production.workstation', 'Work Station') +# printer = fields.Many2One('sale.pos_printer', 'Printer') -class WorkStationPrinter(ModelSQL, ModelView): - "Work Station" - __name__ = 'production.workstation.pos_printer' +class TaskPrinter(ModelSQL, ModelView): + "Task Printer" + __name__ = 'production.task.pos_printer' - work_station = fields.Many2One('production.workstation', 'Work Station') + task = fields.Many2One('production.configuration_task', 'Tasks') printer = fields.Many2One('sale.pos_printer', 'Printer') @@ -40,24 +49,33 @@ class ConfigurationTask(ModelSQL, ModelView): name = fields.Char('Name', states=STATES) description = fields.Text('Description') - ldm = fields.Many2One('production.bom', 'LDM', states=STATES) product = fields.Many2One('product.product', 'Product', search_context={ 'outputs': Eval('_parent_ldm', {}).get('outputs')}) - work_station = fields.Many2One('production.workstation', 'Work Station', states=STATES) + printers = fields.Many2Many('production.task.pos_printer', 'task', + 'printer', 'Printers', states=STATES) + # for remove + # ldm = fields.Many2One('production.bom', 'LDM', states=STATES) + # work_station = fields.Many2One('production.workstation', 'Work Station', states=STATES) @classmethod - def create(cls, vlist): - pool = Pool() - LDM = pool.get('production.bom') - vlist = [x.copy() for x in vlist] - for vals in vlist: - if 'ldm' in vals: - ldm = LDM(vals['ldm']) - if not ldm.outputs: - raise UserError(gettext('sale_pos_frontend_rest.msg_missing_product_output')) - vals['product'] = ldm.outputs[0].product.id - return super(ConfigurationTask, cls).create(vlist) + def __register__(cls, module_name): + super(ConfigurationTask, cls).__register__(module_name) + + table = cls.__table_handler__(module_name) + if table.column_exist('ldm'): + TaskPrinter = Pool().get('production.task.pos_printer') + query = '''select t.id, p.printer from + production_configuration_task as t + join production_workstation_pos_printer as p + on p.work_station=t.work_station''' + cursor = Transaction().connection.cursor() + cursor.execute(query) + res = cursor.fetchall() + to_create = [{'task': t, 'printer': p} for t, p in res] + TaskPrinter.create(to_create) + table.drop_column('ldm') + table.drop_column('work_station') class Task(ModelSQL, ModelView): @@ -65,17 +83,63 @@ class Task(ModelSQL, ModelView): __name__ = 'production.task' name = fields.Char('Name', states=STATES) - description = fields.Text('Description') - ldm = fields.Many2One('production.bom', 'LDM', states=STATES) - work_station = fields.Many2One('production.workstation', 'Work Station', states=STATES) - planned_date = fields.Date('Planned Date') + note = fields.Text('Note') + line = fields.Many2One('sale.line', 'Sale Line', states={'readonly': True}) quantity = fields.Integer('Quantity') - state = fields.Selection([('pending', 'Pending'), ('done', 'Done')], 'State') + state = fields.Selection([ + ('pending', 'Pending'), + ('commanded', 'Commanded'), + ('done', 'Done')], 'State') + printer = fields.Many2One('sale.pos_printer', 'Printer') + task = fields.Many2One('production.configuration_task', 'Task') + sale_number = fields.Char('Sale Number') + # ldm = fields.Many2One('production.bom', 'LDM', states=STATES) @classmethod def __setup__(cls): super(Task, cls).__setup__() + @classmethod + def get_tasks_to_print(cls, args): + one_hour_ago = str(datetime.now() - timedelta(hours=1)) + dom = [ + ('state', '=', 'pending'), + ('create_date', '>=', one_hour_ago) + ] + if args.get('sale_id'): + dom.append('line.sale', '=', args['sale_id']) + + fields = [ + 'name', 'note', 'quantity', 'sale_number', + 'line', 'line.sale', + 'printer.name', 'printer.shop', 'printer.host', + 'printer.interface', 'printer.port', 'printer.row_characters', + ] + tasks = cls.search_read(dom, fields_names=fields, order=[('id', 'DESC')]) + tasks_print = {} + for t in tasks: + printer_id = t['printer.']['id'] + sale_id = t['line.']['sale'] + key = str(printer_id) + '_' + str(sale_id) + line = { + 'quantity': t['quantity'], + 'name': t['name'], + 'line': t['line.']['id'], + 'task': t['id'], + 'note': t['note'] + } + try: + tasks_print[key]['lines'].append(line) + except Exception: + tasks_print[key] = {} + tasks_print[key]['lines'] = [line] + value_printer = t['printer.'] + value_printer['device'] = value_printer['host'] + value_printer['profile'] = '' + tasks_print[key]['printer'] = t['printer.'] + tasks_print[key]['sale'] = t['sale_number'] + tasks_print[key]['work_station'] = value_printer['name'] + return list(tasks_print.values()) # class Production(metaclass=PoolMeta): # __name__ = 'production' diff --git a/sale.py b/sale.py index 2d1f61c..a45bf90 100644 --- a/sale.py +++ b/sale.py @@ -88,16 +88,24 @@ class Sale(metaclass=PoolMeta): """ Status = Pool().get('sale.order_status.time') Line = Pool().get('sale.line') - sale = cls(args['sale_id']) - to_write = {'order_status': 'commanded'} - now = datetime.now() - order_time = sale.order_status_time - if order_time: - Status.write([order_time], {'commanded': now}) + lines_ = args.get('lines_ids', []) + + if args.get('sale_id'): + sale = cls(args['sale_id']) + sales = [sale] else: - status, = Status.create([{'commanded': now, 'requested': now, 'sale': args['sale_id']}]) - to_write['order_status_time'] = status - cls.write([sale], to_write) + lines = Line.browse(lines_) + sales = set(ln.sale for ln in lines) + for sale in sales: + to_write = {'order_status': 'commanded'} + now = datetime.now() + order_time = sale.order_status_time + if order_time: + Status.write([order_time], {'commanded': now}) + else: + status, = Status.create([{'commanded': now, 'requested': now, 'sale': sale.id}]) + to_write['order_status_time'] = status + cls.write([sale], to_write) if args.get('lines_ids'): lines_ids = args['lines_ids'] lines = Line.browse(lines_ids) @@ -113,22 +121,20 @@ class Sale(metaclass=PoolMeta): @classmethod def get_orders_to_command(cls, args): - + # function deprecated for remove pool = Pool() Line = pool.get('sale.line') - WorkStation = pool.get('production.workstation') shop = args['shop'] - stations = WorkStation.search_read([], - fields_names=[ - "name", "printers.shop", - "printers.host", "printers.interface", - "printers.port", "printers.row_characters"]) - stations = {s['id']: s for s in stations} _date = str(datetime.now() - timedelta(hours=1)) fields = [ 'sale.number', 'quantity', 'note', - 'product.tasks.work_station', + 'product.tasks.printers.shop', + 'product.tasks.printers.name', + 'product.tasks.printers.host', + 'product.tasks.printers.interface', + 'product.tasks.printers.port', + 'product.tasks.printers.row_characters', 'product.tasks.name'] dom = [ ("sale.shop", "=", shop), @@ -149,6 +155,7 @@ class Sale(metaclass=PoolMeta): sale_number = line['sale.']['number'] qty = line['quantity'] note = line['note'] + id_ = str(line['id']) try: data[sale_id] @@ -160,13 +167,17 @@ class Sale(metaclass=PoolMeta): } for t in line['product.']['tasks.']: - line_ = {'qty': qty, 'note': note, 'name': t['name']} - station_id = t['work_station'] - station = stations[station_id] - for p in station['printers.']: - key = str(p['id']) + '_' + str(station_id) + line_ = { + 'quantity': qty, 'note': note, + 'name': t['name'], 'line': id_, + 'task': str(t['id'])} + for p in t['printers.']: + key = str(p['id']) if p['shop'] == shop: - + value = { + **line_, + 'printer': p['id'] + } try: data[sale_id]['tasks'][key]['lines'].append(line_) except Exception: @@ -175,7 +186,7 @@ class Sale(metaclass=PoolMeta): data[sale_id]['tasks'][key] = { 'printer': p, 'sale': sale_number, - 'work_station': station['name'], + 'work_station': p['name'], 'lines': [line_] } @@ -190,10 +201,7 @@ class Sale(metaclass=PoolMeta): return list(data.values()) def get_data_for_stations(self): - # TaskConfig = Pool().get('production.configuration_task') - # Line = Pool().get('sale.line') - # print('ingesa nnn', self.id) - # print(line.status_order, 'status order') + # function deprecated data_grouped_printer = {} if not self.number: self.set_number([self]) @@ -201,10 +209,11 @@ class Sale(metaclass=PoolMeta): shop = self.shop line_commanded = [] for line in self.lines: - if line.status_order in ('draft', 'requested'): + if not line.task_printed: line_commanded.append(line) qty = line.quantity note = line.note + line_id = line.id tasks = line.product.tasks att_getter = attrgetter("name") att_getter_p = attrgetter("host", "interface", "port", "row_characters") @@ -212,7 +221,9 @@ class Sale(metaclass=PoolMeta): station = t.work_station station_id = station.id name = att_getter(t) - value = {'name': name, 'qty': qty, 'note': note} + value = { + 'name': name, 'qty': qty, 'note': note, + 'id': line_id, 'task_id': t.id} for p in t.work_station.printers: key = str(p.id) + '_' + str(station_id) if p.shop == shop: @@ -488,11 +499,196 @@ class SaleLine(metaclass=PoolMeta): __name__ = 'sale.line' production = fields.Many2One('production', 'Production') status_order = fields.Selection(OPTIONS_STATUS, 'Status Order') + without_task = fields.Boolean('Without Task') + tasks = fields.One2Many('production.task', 'line', 'Tasks') @staticmethod def default_status_order(): return 'draft' + @classmethod + def mark_tasks_printed(cls, args): + Task = Pool().get('production.task') + task_ids = args.get('task_ids') + tasks = Task.browse(task_ids) + Task.write(list(tasks), {'state': 'commanded'}) + + @classmethod + def get_data_command_and_task(cls, args): + orders = cls.get_data_command(args) + tasks = cls.get_data_tasks(args) + return {'orders': orders, 'tasks': tasks} + + @classmethod + def get_data_command(cls, args): + Sale = Pool().get('sale.sale') + Printer = Pool().get('sale.pos_printer') + + one_hour_ago = str(datetime.now() - timedelta(hours=1)) + shop = args.get('shop') + + fields_printer = [ + 'shop', 'name', + 'host', 'interface', + 'port', 'row_characters', + ] + printers = Printer.search_read(['shop', '=', shop], + fields_names=fields_printer) + printers = {p['id']: p for p in printers} + dom = [ + ("sale.shop", "=", shop), + ("sale.state", "in", ("draft", "quotation")), + ("status_order", "=", 'requested'), + ("create_date", ">=", one_hour_ago), + ] + if args.get('sale_id'): + dom.append(('sale', '=', args['sale_id'])) + fields = [ + 'sale', 'product.name', + 'product.template.printers', + 'product.template.categories', + 'quantity', 'note' + ] + lines = cls.search_read(dom, fields_names=fields) + + sale_ids = set(ln['sale'] for ln in lines) + fields_sales = [ + 'consumer.name', 'consumer.phone', + 'consumer.address', 'consumer.notes', + 'party.name', 'turn', 'number', 'invoice_number', + 'position', 'salesman.rec_name', 'comment', + 'delivery_charge', 'payment_term.name', 'delivery_amount', + 'total_amount', 'shop.name', 'kind', 'table_assigned.name' + ] + sales = Sale.search_read(['id', 'in', sale_ids], fields_names=fields_sales) + sales = { + s['id']: { + 'id': s['id'], + 'consumer': s['consumer.'], + 'turn': s['turn'], + 'number': s['invoice_number'], + 'sale_number': s['number'], + 'position': s.get('position', ''), + 'party': s.get('party.', {}).get('name'), + 'kind': s['kind'], + 'delivery_amount': s['delivery_amount'], + 'salesman': s.get('salesman.', {}).get('rec_name'), + 'comment': s.get('comment', ''), + 'payment_term': s.get('payment_term.', {}).get('name', ''), + 'delivery_charge': s['delivery_charge'], + 'total_amount': str(s['total_amount']), + 'shop': s.get('shop.', {}).get('name', ''), + 'table_assigned': s.get('table_assigned.', {}).get('name', ''), + } + for s in sales + } + orders = {} + lines_mark_sended = [] + for line in lines: + sale_id = line['sale'] + sale = sales[sale_id] + printers_ = line['product.']['template.'].get('printers', []) + + if not printers_: + lines_mark_sended.append(line['id']) + continue + + value_line = { + 'name': line['product.']['name'], + 'quantity': str(line['quantity']), + 'unit_price': int(0), # validate is this field is neccesary + 'note': line['note'], + 'id': line['id'] + } + categories = line['product.']['template.'].get('categories') + for printer_id in printers_: + printer = printers.get(printer_id) + if not printer: + continue + + key = f'{sale_id}_{printer_id}' + + if key not in orders: + orders[key] = {**printer, **sale, 'lines': []} + + if 'categories.' in printer: + orders[key]['lines'] = { + c['sequence']: { + 'name': c.get('category.', {}).get('name'), + 'lines': []} for c in printer['categories']} + orders[key]['categories'] = {c['category.']['id']: c['sequence'] for c in printer['categories.']} + + if isinstance(orders[key]['lines'], list): + orders[key]['lines'].append(value_line) + else: + key_id = orders[key]['categories'].get(categories[0], 'others') + + if 'others' not in orders[key]['lines']: + orders[key]['lines']['others'] = {'name': 'OTROS', 'lines': []} + + orders[key]['lines'][key_id]['lines'].append(value_line) + return list(orders.values()) + + @classmethod + def get_data_tasks(cls, args): + Task = Pool().get('production.task') + one_hour_ago = str(datetime.now() - timedelta(hours=1)) + shop = args['shop'] + fields = [ + 'sale.number', 'quantity', 'note', + 'product.tasks.printers.shop', + 'product.tasks.printers.name', + 'product.tasks.printers.host', + 'product.tasks.printers.interface', + 'product.tasks.printers.port', + 'product.tasks.printers.row_characters', + 'product.tasks.name'] + dom = [ + ("sale.shop", "=", shop), + ("sale.state", "in", ("draft", "quotation")), + ("create_date", ">=", one_hour_ago), + ("without_task", "!=", True), + ("tasks", '=', None) + ] + if args.get('sale_id'): + dom.append(('sale', '=', args['sale'])) + lines = cls.search_read( + dom, fields_names=fields, + order=[('sale', 'ASC'), ('id', 'DESC')]) + + to_create = [] + lines_without_tasks = [] + print(lines, 'validate tasks') + for line in lines: + quantity = line['quantity'] + note = line['note'] + line_id = line['id'] + sale_number = line['sale.']['number'] + tasks = line['product.']['tasks.'] + if not tasks: + lines_without_tasks.append(line_id) + + for t in line['product.']['tasks.']: + task_id = t['id'] + for p in t['printers.']: + if p['shop'] == shop: + value = { + 'quantity': quantity, + 'note': note, + 'line': line_id, + 'name': t['name'], + 'sale_number': sale_number, + 'task': task_id, + 'state': 'pending', + 'printer': p['id']} + to_create.append(value) + if lines_without_tasks: + lines_ = cls.browse(lines_without_tasks) + cls.write(list(lines_), {'without_task': True}) + Task.create(to_create) + data = Task.get_tasks_to_print(args) + return data + @classmethod def delete(cls, lines): HistoryDelete = Pool().get('sale.line._history.delete') diff --git a/tryton.cfg b/tryton.cfg index 80846ae..8ea37bf 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.6 +version=6.0.7 depends: sale production_accounting diff --git a/view/bom_form.xml b/view/bom_form.xml index 5a1eb3a..d49b669 100644 --- a/view/bom_form.xml +++ b/view/bom_form.xml @@ -3,9 +3,9 @@ this repository contains the full copyright notices and license terms. --> - + \ No newline at end of file diff --git a/view/configuration_task_form.xml b/view/configuration_task_form.xml index 760a851..60b92d6 100644 --- a/view/configuration_task_form.xml +++ b/view/configuration_task_form.xml @@ -4,13 +4,15 @@ The COPYRIGHT file at the top level of this repository contains the full copyrig