Ejemplo n.º 1
0
class Template(ModelSQL, ModelView):
    "Product Template"
    __name__ = "product.template"
    name = fields.Char('Name', size=None, required=True, translate=True,
        select=True, states=STATES, depends=DEPENDS)
    type = fields.Selection(TYPES, 'Type', required=True, states=STATES,
        depends=DEPENDS)
    consumable = fields.Boolean('Consumable',
        states={
            'readonly': ~Eval('active', True),
            'invisible': Eval('type', 'goods') != 'goods',
            },
        depends=['active', 'type'])
    list_price = fields.Property(fields.Numeric('List Price', states=STATES,
            digits=price_digits, depends=DEPENDS, required=True))
    cost_price = fields.Property(fields.Numeric('Cost Price', states=STATES,
            digits=price_digits, depends=DEPENDS, required=True))
    cost_price_method = fields.Property(fields.Selection(COST_PRICE_METHODS,
            'Cost Method', required=True, states=STATES, depends=DEPENDS))
    default_uom = fields.Many2One('product.uom', 'Default UOM', required=True,
        states=STATES, depends=DEPENDS)
    default_uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Default UOM Category'),
        'on_change_with_default_uom_category',
        searcher='search_default_uom_category')
    active = fields.Boolean('Active', select=True)
    categories = fields.Many2Many('product.template-product.category',
        'template', 'category', 'Categories', states=STATES, depends=DEPENDS)
    products = fields.One2Many('product.product', 'template', 'Variants',
        states=STATES, depends=DEPENDS)

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().connection.cursor()
        sql_table = cls.__table__()

        super(Template, cls).__register__(module_name)

        table = TableHandler(cls, module_name)
        # Migration from 2.2: category is no more required
        table.not_null_action('category', 'remove')

        # Migration from 2.2: new types
        cursor.execute(*sql_table.update(
                columns=[sql_table.consumable],
                values=[True],
                where=sql_table.type == 'consumable'))
        cursor.execute(*sql_table.update(
                columns=[sql_table.type],
                values=['goods'],
                where=sql_table.type.in_(['stockable', 'consumable'])))

        # Migration from 3.8: rename category into categories
        if table.column_exist('category'):
            logger.warning(
                'The column "category" on table "%s" must be dropped manually',
                cls._table)

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_type():
        return 'goods'

    @staticmethod
    def default_consumable():
        return False

    @staticmethod
    def default_products():
        if Transaction().user == 0:
            return []
        return [{}]

    @fields.depends('default_uom')
    def on_change_with_default_uom_category(self, name=None):
        if self.default_uom:
            return self.default_uom.category.id

    @classmethod
    def search_default_uom_category(cls, name, clause):
        return [('default_uom.category',) + tuple(clause[1:])]

    @classmethod
    def create(cls, vlist):
        vlist = [v.copy() for v in vlist]
        for values in vlist:
            values.setdefault('products', None)
        return super(Template, cls).create(vlist)

    @classmethod
    def search_global(cls, text):
        for record, rec_name, icon in super(Template, cls).search_global(text):
            icon = icon or 'tryton-product'
            yield record, rec_name, icon
Ejemplo n.º 2
0
class Production(Workflow, ModelSQL, ModelView):
    "Production"
    __name__ = 'production'
    _rec_name = 'number'

    number = fields.Char('Number', select=True, readonly=True)
    reference = fields.Char('Reference',
                            select=1,
                            states={
                                'readonly':
                                ~Eval('state').in_(['request', 'draft']),
                            },
                            depends=['state'])
    planned_date = fields.Date('Planned Date',
                               states={
                                   'readonly':
                                   ~Eval('state').in_(['request', 'draft']),
                               },
                               depends=['state'])
    effective_date = fields.Date('Effective Date',
                                 states={
                                     'readonly':
                                     Eval('state').in_(['cancel', 'done']),
                                 },
                                 depends=['state'])
    planned_start_date = fields.Date(
        'Planned Start Date',
        states={
            'readonly': ~Eval('state').in_(['request', 'draft']),
            'required': Bool(Eval('planned_date')),
        },
        depends=['state', 'planned_date'])
    effective_start_date = fields.Date('Effective Start Date',
                                       states={
                                           'readonly':
                                           Eval('state').in_(
                                               ['cancel', 'running', 'done']),
                                       },
                                       depends=['state'])
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              states={
                                  'readonly':
                                  ~Eval('state').in_(['request', 'draft']),
                              },
                              depends=['state'])
    warehouse = fields.Many2One(
        'stock.location',
        'Warehouse',
        required=True,
        domain=[
            ('type', '=', 'warehouse'),
        ],
        states={
            'readonly': (~Eval('state').in_(['request', 'draft'])
                         | Eval('inputs', True) | Eval('outputs', True)),
        },
        depends=['state'])
    location = fields.Many2One(
        'stock.location',
        'Location',
        required=True,
        domain=[
            ('type', '=', 'production'),
        ],
        states={
            'readonly': (~Eval('state').in_(['request', 'draft'])
                         | Eval('inputs', True) | Eval('outputs', True)),
        },
        depends=['state'])
    product = fields.Many2One('product.product',
                              'Product',
                              domain=[
                                  ('producible', '=', True),
                              ],
                              states={
                                  'readonly':
                                  ~Eval('state').in_(['request', 'draft']),
                              })
    bom = fields.Many2One('production.bom',
                          'BOM',
                          domain=[
                              ('output_products', '=', Eval('product', 0)),
                          ],
                          states={
                              'readonly':
                              (~Eval('state').in_(['request', 'draft'])
                               | ~Eval('warehouse', 0) | ~Eval('location', 0)),
                              'invisible':
                              ~Eval('product'),
                          },
                          depends=['product'])
    uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Uom Category'),
        'on_change_with_uom_category')
    uom = fields.Many2One('product.uom',
                          'Uom',
                          domain=[
                              ('category', '=', Eval('uom_category')),
                          ],
                          states={
                              'readonly':
                              ~Eval('state').in_(['request', 'draft']),
                              'required': Bool(Eval('bom')),
                              'invisible': ~Eval('product'),
                          },
                          depends=['uom_category'])
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
                                  'on_change_with_unit_digits')
    quantity = fields.Float('Quantity',
                            digits=(16, Eval('unit_digits', 2)),
                            states={
                                'readonly':
                                ~Eval('state').in_(['request', 'draft']),
                                'required':
                                Bool(Eval('bom')),
                                'invisible':
                                ~Eval('product'),
                            },
                            depends=['unit_digits'])
    cost = fields.Function(
        fields.Numeric('Cost', digits=price_digits, readonly=True), 'get_cost')
    inputs = fields.One2Many(
        'stock.move',
        'production_input',
        'Inputs',
        domain=[
            ('shipment', '=', None),
            ('from_location', 'child_of', [Eval('warehouse')], 'parent'),
            ('to_location', '=', Eval('location')),
            ('company', '=', Eval('company', -1)),
        ],
        states={
            'readonly': (~Eval('state').in_(['request', 'draft', 'waiting'])
                         | ~Eval('warehouse') | ~Eval('location')),
        },
        depends=['warehouse', 'location', 'company'])
    outputs = fields.One2Many('stock.move',
                              'production_output',
                              'Outputs',
                              domain=[
                                  ('shipment', '=', None),
                                  ('from_location', '=', Eval('location')),
                                  ('to_location', 'child_of',
                                   [Eval('warehouse')], 'parent'),
                                  ('company', '=', Eval('company', -1)),
                              ],
                              states={
                                  'readonly':
                                  (Eval('state').in_(['done', 'cancel'])
                                   | ~Eval('warehouse') | ~Eval('location')),
                              },
                              depends=['warehouse', 'location', 'company'])
    state = fields.Selection([
        ('request', 'Request'),
        ('draft', 'Draft'),
        ('waiting', 'Waiting'),
        ('assigned', 'Assigned'),
        ('running', 'Running'),
        ('done', 'Done'),
        ('cancel', 'Canceled'),
    ],
                             'State',
                             readonly=True)

    @classmethod
    def __setup__(cls):
        super(Production, cls).__setup__()
        cls._error_messages.update({
            'uneven_costs':
            ('The costs of the outputs (%(outputs)s) of '
             'production "%(production)s" do not match the cost of the '
             'production (%(costs)s).')
        })
        cls._transitions |= set((
            ('request', 'draft'),
            ('draft', 'waiting'),
            ('waiting', 'assigned'),
            ('assigned', 'running'),
            ('running', 'done'),
            ('running', 'waiting'),
            ('assigned', 'waiting'),
            ('waiting', 'waiting'),
            ('waiting', 'draft'),
            ('request', 'cancel'),
            ('draft', 'cancel'),
            ('waiting', 'cancel'),
            ('assigned', 'cancel'),
            ('cancel', 'draft'),
        ))
        cls._buttons.update({
            'cancel': {
                'invisible':
                ~Eval('state').in_(['request', 'draft', 'assigned']),
            },
            'draft': {
                'invisible':
                ~Eval('state').in_(['request', 'waiting', 'cancel']),
                'icon':
                If(
                    Eval('state') == 'cancel', 'tryton-clear',
                    If(
                        Eval('state') == 'request', 'tryton-go-next',
                        'tryton-go-previous')),
            },
            'reset_bom': {
                'invisible':
                (~Eval('bom')
                 | ~Eval('state').in_(['request', 'draft', 'waiting'])),
            },
            'wait': {
                'invisible':
                ~Eval('state').in_(['draft', 'assigned', 'waiting', 'running'
                                    ]),
                'icon':
                If(
                    Eval('state').in_(['assigned', 'running']),
                    'tryton-go-previous',
                    If(
                        Eval('state') == 'waiting', 'tryton-clear',
                        'tryton-go-next')),
            },
            'run': {
                'invisible': Eval('state') != 'assigned',
            },
            'done': {
                'invisible': Eval('state') != 'running',
            },
            'assign_wizard': {
                'invisible': Eval('state') != 'waiting',
            },
            'assign_try': {},
            'assign_force': {},
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        table_h = TableHandler(cls, module_name)
        table = cls.__table__()

        # Migration from 3.8: rename code into number
        if table_h.column_exist('code'):
            table_h.column_rename('code', 'number')

        super(Production, cls).__register__(module_name)

        # Migration from 4.0: fill planned_start_date
        cursor = Transaction().connection.cursor()
        cursor.execute(
            *table.update([table.planned_start_date], [table.planned_date],
                          where=(table.planned_start_date == Null)
                          & (table.planned_date != Null)))

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def default_warehouse(cls):
        Location = Pool().get('stock.location')
        locations = Location.search(cls.warehouse.domain)
        if len(locations) == 1:
            return locations[0].id

    @classmethod
    def default_location(cls):
        Location = Pool().get('stock.location')
        warehouse_id = cls.default_warehouse()
        if warehouse_id:
            warehouse = Location(warehouse_id)
            return warehouse.production_location.id

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @fields.depends('planned_date', 'product', 'bom')
    def on_change_with_planned_start_date(self, pattern=None):
        if self.planned_date and self.product:
            if pattern is None:
                pattern = {}
            pattern.setdefault('bom', self.bom.id if self.bom else None)
            for line in self.product.lead_times:
                if line.match(pattern):
                    if line.lead_time:
                        return self.planned_date - line.lead_time
                    else:
                        return self.planned_date
        return self.planned_date

    def _move(self, from_location, to_location, company, product, uom,
              quantity):
        Move = Pool().get('stock.move')
        move = Move(
            product=product,
            uom=uom,
            quantity=quantity,
            from_location=from_location,
            to_location=to_location,
            company=company,
            currency=company.currency if company else None,
            state='draft',
        )
        return move

    def _explode_move_values(self, from_location, to_location, company, bom_io,
                             quantity):
        move = self._move(from_location, to_location, company, bom_io.product,
                          bom_io.uom, quantity)
        move.from_location = from_location.id if from_location else None
        move.to_location = to_location.id if to_location else None
        move.unit_price_required = move.on_change_with_unit_price_required()
        return move

    def explode_bom(self):
        pool = Pool()
        Uom = pool.get('product.uom')
        Move = pool.get('stock.move')
        if not (self.bom and self.product and self.uom):
            return
        self.cost = Decimal(0)

        if self.warehouse:
            storage_location = self.warehouse.storage_location
        else:
            storage_location = None

        factor = self.bom.compute_factor(self.product, self.quantity or 0,
                                         self.uom)
        inputs = []
        for input_ in self.bom.inputs:
            quantity = input_.compute_quantity(factor)
            move = self._explode_move_values(storage_location, self.location,
                                             self.company, input_, quantity)
            if move:
                inputs.append(move)
                quantity = Uom.compute_qty(input_.uom,
                                           quantity,
                                           input_.product.default_uom,
                                           round=False)
                self.cost += (Decimal(str(quantity)) *
                              input_.product.cost_price)
        self.inputs = inputs
        digits = self.__class__.cost.digits
        self.cost = self.cost.quantize(Decimal(str(10**-digits[1])))

        digits = Move.unit_price.digits
        digit = Decimal(str(10**-digits[1]))
        outputs = []
        for output in self.bom.outputs:
            quantity = output.compute_quantity(factor)
            move = self._explode_move_values(self.location, storage_location,
                                             self.company, output, quantity)
            if move:
                move.unit_price = Decimal(0)
                if output.product == move.product and quantity:
                    move.unit_price = Decimal(
                        self.cost / Decimal(str(quantity))).quantize(digit)
                outputs.append(move)
        self.outputs = outputs

    @fields.depends('warehouse')
    def on_change_warehouse(self):
        self.location = None
        if self.warehouse:
            self.location = self.warehouse.production_location

    @fields.depends(*BOM_CHANGES)
    def on_change_product(self):
        if self.product:
            category = self.product.default_uom.category
            if not self.uom or self.uom.category != category:
                self.uom = self.product.default_uom
                self.unit_digits = self.product.default_uom.digits
        else:
            self.bom = None
            self.uom = None
            self.unit_digits = 2
        self.explode_bom()

    @fields.depends('product')
    def on_change_with_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom.category.id

    @fields.depends('uom')
    def on_change_with_unit_digits(self, name=None):
        if self.uom:
            return self.uom.digits
        return 2

    @fields.depends(*BOM_CHANGES)
    def on_change_bom(self):
        self.explode_bom()

    @fields.depends(*BOM_CHANGES)
    def on_change_uom(self):
        self.explode_bom()

    @fields.depends(*BOM_CHANGES)
    def on_change_quantity(self):
        self.explode_bom()

    @ModelView.button_change(*BOM_CHANGES)
    def reset_bom(self):
        self.explode_bom()

    def get_cost(self, name):
        cost = Decimal(0)
        for input_ in self.inputs:
            if input_.cost_price is not None:
                cost_price = input_.cost_price
            else:
                cost_price = input_.product.cost_price
            cost += (Decimal(str(input_.internal_quantity)) * cost_price)

        digits = self.__class__.cost.digits
        return cost.quantize(Decimal(str(10**-digits[1])))

    @fields.depends('inputs')
    def on_change_with_cost(self):
        Uom = Pool().get('product.uom')

        cost = Decimal(0)
        if not self.inputs:
            return cost

        for input_ in self.inputs:
            if (input_.product is None or input_.uom is None
                    or input_.quantity is None):
                continue
            product = input_.product
            quantity = Uom.compute_qty(input_.uom, input_.quantity,
                                       product.default_uom)
            cost += Decimal(str(quantity)) * product.cost_price
        return cost

    def set_moves(self):
        pool = Pool()
        Move = pool.get('stock.move')

        storage_location = self.warehouse.storage_location
        location = self.location
        company = self.company

        if not self.bom:
            if self.product:
                move = self._move(location, storage_location, company,
                                  self.product, self.uom, self.quantity)
                if move:
                    move.production_output = self
                    move.unit_price = Decimal(0)
                    move.save()
            self._set_move_planned_date()
            return

        factor = self.bom.compute_factor(self.product, self.quantity, self.uom)
        cost = Decimal(0)
        for input_ in self.bom.inputs:
            quantity = input_.compute_quantity(factor)
            product = input_.product
            move = self._move(storage_location, location, company, product,
                              input_.uom, quantity)
            if move:
                move.production_input = self
                move.save()
                cost += (Decimal(str(move.internal_quantity)) *
                         product.cost_price)
        digits = self.__class__.cost.digits
        cost = cost.quantize(Decimal(str(10**-digits[1])))

        digits = Move.unit_price.digits
        digit = Decimal(str(10**-digits[1]))
        for output in self.bom.outputs:
            quantity = output.compute_quantity(factor)
            product = output.product
            move = self._move(location, storage_location, company, product,
                              output.uom, quantity)
            if move:
                move.production_output = self
                if product == self.product:
                    move.unit_price = Decimal(
                        cost / Decimal(str(quantity))).quantize(digit)
                else:
                    move.unit_price = Decimal(0)
                move.save()
        self._set_move_planned_date()

    @classmethod
    def validate(cls, productions):
        super(Production, cls).validate(productions)
        for production in productions:
            production.check_cost()

    def check_cost(self):
        if self.state != 'done':
            return
        cost_price = Decimal(0)
        for output in self.outputs:
            cost_price += (Decimal(str(output.quantity)) * output.unit_price)
        if not self.company.currency.is_zero(self.cost - cost_price):
            self.raise_user_error(
                'uneven_costs', {
                    'production': self.rec_name,
                    'costs': self.cost,
                    'outputs': cost_price,
                })

    @classmethod
    def create(cls, vlist):
        Sequence = Pool().get('ir.sequence')
        Config = Pool().get('production.configuration')

        vlist = [x.copy() for x in vlist]
        config = Config(1)
        for values in vlist:
            if values.get('number') is None:
                values['number'] = Sequence.get_id(
                    config.production_sequence.id)
        productions = super(Production, cls).create(vlist)
        for production in productions:
            production._set_move_planned_date()
        return productions

    @classmethod
    def write(cls, *args):
        super(Production, cls).write(*args)
        for production in sum(args[::2], []):
            production._set_move_planned_date()

    @classmethod
    def copy(cls, productions, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default.setdefault('number', None)
        return super(Production, cls).copy(productions, default=default)

    def _get_move_planned_date(self):
        "Return the planned dates for input and output moves"
        return self.planned_start_date, self.planned_date

    def _set_move_planned_date(self):
        "Set planned date of moves for the shipments"
        pool = Pool()
        Move = pool.get('stock.move')
        dates = self._get_move_planned_date()
        input_date, output_date = dates
        Move.write([
            m for m in self.inputs
            if m.state not in ('assigned', 'done', 'cancel')
        ], {
            'planned_date': input_date,
        })
        Move.write([
            m for m in self.outputs
            if m.state not in ('assigned', 'done', 'cancel')
        ], {
            'planned_date': output_date,
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        Move.cancel([m for p in productions for m in p.inputs + p.outputs])

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        Move.draft([m for p in productions for m in p.inputs + p.outputs])

    @classmethod
    @ModelView.button
    @Workflow.transition('waiting')
    def wait(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        Move.draft([m for p in productions for m in p.inputs + p.outputs])

    @classmethod
    @Workflow.transition('assigned')
    def assign(cls, productions):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('running')
    def run(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        Date = pool.get('ir.date')
        Move.do([m for p in productions for m in p.inputs])
        cls.write([p for p in productions if not p.effective_start_date], {
            'effective_start_date': Date.today(),
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def done(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        Date = pool.get('ir.date')
        Move.do([m for p in productions for m in p.outputs])
        cls.write([p for p in productions if not p.effective_date], {
            'effective_date': Date.today(),
        })

    @classmethod
    @ModelView.button_action('production.wizard_assign')
    def assign_wizard(self, productions):
        pass

    @classmethod
    @ModelView.button
    def assign_try(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        if Move.assign_try([m for p in productions for m in p.inputs]):
            cls.assign(productions)
            return True
        else:
            return False

    @classmethod
    @ModelView.button
    def assign_force(cls, productions):
        pool = Pool()
        Move = pool.get('stock.move')
        Move.assign([m for p in productions for m in p.inputs])
        cls.assign(productions)
Ejemplo n.º 3
0
class Product(ModelSQL, ModelView):
    "Product Variant"
    __name__ = "product.product"
    _order_name = 'rec_name'
    template = fields.Many2One('product.template', 'Product Template',
        required=True, ondelete='CASCADE', select=True, states=STATES,
        depends=DEPENDS)
    code = fields.Char("Code", size=None, select=True, states=STATES,
        depends=DEPENDS)
    description = fields.Text("Description", translate=True, states=STATES,
        depends=DEPENDS)
    active = fields.Boolean('Active', select=True)
    list_price_uom = fields.Function(fields.Numeric('List Price',
        digits=price_digits), 'get_price_uom')
    cost_price_uom = fields.Function(fields.Numeric('Cost Price',
        digits=price_digits), 'get_price_uom')

    @classmethod
    def __setup__(cls):
        pool = Pool()
        Template = pool.get('product.template')

        if not hasattr(cls, '_no_template_field'):
            cls._no_template_field = set()
        cls._no_template_field.update(['products'])

        super(Product, cls).__setup__()

        for attr in dir(Template):
            tfield = getattr(Template, attr)
            if not isinstance(tfield, fields.Field):
                continue
            if attr in cls._no_template_field:
                continue
            field = getattr(cls, attr, None)
            if not field or isinstance(field, TemplateFunction):
                setattr(cls, attr, TemplateFunction(copy.deepcopy(tfield)))
                order_method = getattr(cls, 'order_%s' % attr, None)
                if (not order_method
                        and not isinstance(tfield, (
                                fields.Function,
                                fields.One2Many,
                                fields.Many2Many))):
                    order_method = TemplateFunction.order(attr)
                    setattr(cls, 'order_%s' % attr, order_method)

    @fields.depends('template')
    def on_change_template(self):
        for name, field in self._fields.iteritems():
            if isinstance(field, TemplateFunction):
                if self.template:
                    value = getattr(self.template, name, None)
                else:
                    value = None
                setattr(self, name, value)

    def get_template(self, name):
        value = getattr(self.template, name)
        if isinstance(value, Model):
            return value.id
        elif (isinstance(value, (list, tuple))
                and value and isinstance(value[0], Model)):
            return [r.id for r in value]
        else:
            return value

    @classmethod
    def search_template(cls, name, clause):
        return [('template.%s' % name,) + tuple(clause[1:])]

    @classmethod
    def order_rec_name(cls, tables):
        pool = Pool()
        Template = pool.get('product.template')
        product, _ = tables[None]
        if 'template' not in tables:
            template = Template.__table__()
            tables['template'] = {
                None: (template, product.template == template.id),
                }
        else:
            template = tables['template']
        return [product.code] + Template.name.convert_order('name',
            tables['template'], Template)

    @staticmethod
    def default_active():
        return True

    def get_rec_name(self, name):
        if self.code:
            return '[' + self.code + '] ' + self.name
        else:
            return self.name

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
            ('code',) + tuple(clause[1:]),
            ('template.name',) + tuple(clause[1:]),
            ]

    @staticmethod
    def get_price_uom(products, name):
        Uom = Pool().get('product.uom')
        res = {}
        field = name[:-4]
        if Transaction().context.get('uom'):
            to_uom = Uom(Transaction().context['uom'])
            for product in products:
                res[product.id] = Uom.compute_price(
                    product.default_uom, getattr(product, field), to_uom)
        else:
            for product in products:
                res[product.id] = getattr(product, field)
        return res

    @classmethod
    def search_global(cls, text):
        for id_, rec_name, icon in super(Product, cls).search_global(text):
            icon = icon or 'tryton-product'
            yield id_, rec_name, icon
Ejemplo n.º 4
0
class View(ModelSQL, ModelView):
    "View"
    __name__ = 'ir.ui.view'
    _rec_name = 'model'
    model = fields.Char('Model',
                        select=True,
                        states={
                            'required':
                            Eval('type').in_([None, 'tree', 'form', 'graph']),
                        })
    priority = fields.Integer('Priority', required=True, select=True)
    type = fields.Selection([
        (None, ''),
        ('tree', 'Tree'),
        ('form', 'Form'),
        ('graph', 'Graph'),
        ('calendar', 'Calendar'),
        ('board', 'Board'),
    ],
                            'View Type',
                            select=True,
                            domain=[
                                If(Bool(Eval('inherit')), ('type', '=', None),
                                   ('type', '!=', None)),
                            ],
                            depends=['inherit'])
    data = fields.Text('Data')
    name = fields.Char('Name',
                       states={
                           'invisible': ~(Eval('module') & Eval('name')),
                       },
                       depends=['module'],
                       readonly=True)
    arch = fields.Function(fields.Text('View Architecture',
                                       states={
                                           'readonly': Bool(Eval('name')),
                                       },
                                       depends=['name']),
                           'get_arch',
                           setter='set_arch')
    inherit = fields.Many2One('ir.ui.view',
                              'Inherited View',
                              select=True,
                              ondelete='CASCADE')
    field_childs = fields.Char('Children Field',
                               states={
                                   'invisible': Eval('type') != 'tree',
                               },
                               depends=['type'])
    module = fields.Char('Module',
                         states={
                             'invisible': ~Eval('module'),
                         },
                         readonly=True)
    domain = fields.Char('Domain',
                         states={
                             'invisible': ~Eval('inherit'),
                         },
                         depends=['inherit'])
    _get_rng_cache = Cache('ir_ui_view.get_rng')

    @classmethod
    def __setup__(cls):
        super(View, cls).__setup__()
        cls._error_messages.update({
            'invalid_xml': 'Invalid XML for view "%s".',
        })
        cls._order.insert(0, ('priority', 'ASC'))
        cls._buttons.update({
            'show': {
                'readonly': Eval('type') != 'form',
            },
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        table = TableHandler(cls, module_name)

        # Migration from 2.4 arch moved into data
        if table.column_exist('arch'):
            table.column_rename('arch', 'data')

        super(View, cls).__register__(module_name)

        # New instance to refresh definition
        table = TableHandler(cls, module_name)

        # Migration from 1.0 arch no more required
        table.not_null_action('arch', action='remove')

        # Migration from 2.4 model no more required
        table.not_null_action('model', action='remove')

    @staticmethod
    def default_priority():
        return 16

    @staticmethod
    def default_module():
        return Transaction().context.get('module') or ''

    @classmethod
    @ModelView.button_action('ir.act_view_show')
    def show(cls, views):
        pass

    @classmethod
    def get_rng(cls, type_):
        key = (cls.__name__, type_)
        rng = cls._get_rng_cache.get(key)
        if rng is None:
            if sys.version_info < (3, ):
                filename = __file__.decode(sys.getfilesystemencoding())
            else:
                filename = __file__
            rng_name = os.path.join(os.path.dirname(filename), type_ + '.rng')
            with open(rng_name, 'rb') as fp:
                rng = etree.fromstring(fp.read())
            cls._get_rng_cache.set(key, rng)
        return rng

    @property
    def rng_type(self):
        if self.inherit:
            return self.inherit.rng_type
        return self.type

    @classmethod
    def validate(cls, views):
        super(View, cls).validate(views)
        cls.check_xml(views)

    @classmethod
    def check_xml(cls, views):
        "Check XML"
        for view in views:
            if not view.arch:
                continue
            xml = view.arch.strip()
            if not xml:
                continue
            tree = etree.fromstring(xml)

            if hasattr(etree, 'RelaxNG'):
                validator = etree.RelaxNG(etree=cls.get_rng(view.rng_type))
                if not validator.validate(tree):
                    error_log = '\n'.join(
                        map(str, validator.error_log.filter_from_errors()))
                    logger.error('Invalid XML view %s:\n%s\n%s', view.rec_name,
                                 error_log, xml)
                    cls.raise_user_error('invalid_xml', (view.rec_name, ),
                                         error_log)
            root_element = tree.getroottree().getroot()

            # validate pyson attributes
            validates = {
                'states': fields.states_validate,
            }

            def encode(element):
                for attr in ('states', 'domain', 'spell'):
                    if not element.get(attr):
                        continue
                    try:
                        value = PYSONDecoder().decode(element.get(attr))
                        validates.get(attr, lambda a: True)(value)
                    except Exception, e:
                        error_log = '%s: <%s %s="%s"/>' % (
                            e, element.get('id')
                            or element.get('name'), attr, element.get(attr))
                        logger.error('Invalid XML view %s:\n%s\n%s',
                                     view.rec_name, error_log, xml)
                        cls.raise_user_error('invalid_xml', (view.rec_name, ),
                                             error_log)
                for child in element:
                    encode(child)

            encode(root_element)
Ejemplo n.º 5
0
class ViewTreeState(ModelSQL, ModelView):
    'View Tree State'
    __name__ = 'ir.ui.view_tree_state'
    _rec_name = 'model'
    model = fields.Char('Model', required=True)
    domain = fields.Char('Domain', required=True)
    user = fields.Many2One('res.user',
                           'User',
                           required=True,
                           ondelete='CASCADE')
    child_name = fields.Char('Child Name')
    nodes = fields.Text('Expanded Nodes')
    selected_nodes = fields.Text('Selected Nodes')

    @classmethod
    def __setup__(cls):
        super(ViewTreeState, cls).__setup__()
        cls.__rpc__.update({
            'set': RPC(readonly=False, check_access=False),
            'get': RPC(check_access=False),
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        table = TableHandler(cls, module_name)

        # Migration from 2.8: table name changed
        table.table_rename('ir_ui_view_tree_expanded_state', cls._table)

        super(ViewTreeState, cls).__register__(module_name)

        table = TableHandler(cls, module_name)
        table.index_action(['model', 'domain', 'user', 'child_name'], 'add')

    @staticmethod
    def default_nodes():
        return '[]'

    @staticmethod
    def default_selected_nodes():
        return '[]'

    @classmethod
    def set(cls, model, domain, child_name, nodes, selected_nodes):
        # Normalize the json domain
        domain = json.dumps(json.loads(domain), separators=(',', ':'))
        current_user = Transaction().user
        records = cls.search([
            ('user', '=', current_user),
            ('model', '=', model),
            ('domain', '=', domain),
            ('child_name', '=', child_name),
        ])
        cls.delete(records)
        cls.create([{
            'user': current_user,
            'model': model,
            'domain': domain,
            'child_name': child_name,
            'nodes': nodes,
            'selected_nodes': selected_nodes,
        }])

    @classmethod
    def get(cls, model, domain, child_name):
        # Normalize the json domain
        domain = json.dumps(json.loads(domain), separators=(',', ':'))
        current_user = Transaction().user
        try:
            expanded_info, = cls.search([
                ('user', '=', current_user),
                ('model', '=', model),
                ('domain', '=', domain),
                ('child_name', '=', child_name),
            ],
                                        limit=1)
        except ValueError:
            return (cls.default_nodes(), cls.default_selected_nodes())
        state = cls(expanded_info)
        return (state.nodes or cls.default_nodes(), state.selected_nodes
                or cls.default_selected_nodes())
Ejemplo n.º 6
0
class Menu(ModelSQL, ModelView):
    "Nereid CMS Menu"
    __name__ = 'nereid.cms.menu'

    name = fields.Char('Name',
                       required=True,
                       on_change=['name', 'unique_identifier'],
                       depends=['name', 'unique_identifier'])
    unique_identifier = fields.Char('Unique Identifier',
                                    required=True,
                                    select=True)
    description = fields.Text('Description')
    website = fields.Many2One('nereid.website', 'WebSite')
    active = fields.Boolean('Active')

    model = fields.Many2One('ir.model', 'Tryton Model', required=True)
    children_field = fields.Many2One('ir.model.field',
                                     'Children',
                                     depends=['model'],
                                     domain=[('model', '=', Eval('model')),
                                             ('ttype', '=', 'one2many')],
                                     required=True)
    uri_field = fields.Many2One('ir.model.field',
                                'URI Field',
                                depends=['model'],
                                domain=[('model', '=', Eval('model')),
                                        ('ttype', '=', 'char')],
                                required=True)
    title_field = fields.Many2One('ir.model.field',
                                  'Title Field',
                                  depends=['model'],
                                  domain=[('model', '=', Eval('model')),
                                          ('ttype', '=', 'char')],
                                  required=True)
    identifier_field = fields.Many2One('ir.model.field',
                                       'Identifier Field',
                                       depends=['model'],
                                       domain=[('model', '=', Eval('model')),
                                               ('ttype', '=', 'char')],
                                       required=True)

    @staticmethod
    def default_active():
        """
        By Default the Menu is active
        """
        return True

    @classmethod
    def __setup__(cls):
        super(Menu, cls).__setup__()
        cls._sql_constraints += [
            ('unique_identifier', 'UNIQUE(unique_identifier, website)',
             'The Unique Identifier of the Menu must be unique.'),
        ]

    def _menu_item_to_dict(self, menu_item):
        """
        :param menu_item: Active record of the menu item
        """
        if hasattr(menu_item, 'reference') and getattr(menu_item, 'reference'):
            model, id = getattr(menu_item, 'reference').split(',')
            if int(id):
                reference, = Pool().get(model)(int(id))
                uri = url_for('%s.render' % reference.__name__,
                              uri=reference.uri)
            else:
                uri = getattr(menu_item, self.uri_field.name)
        else:
            uri = getattr(menu_item, self.uri_field.name)
        return {
            'name': getattr(menu_item, self.title_field.name),
            'uri': uri,
        }

    def _generate_menu_tree(self, menu_item):
        """
        :param menu_item: Active record of the root menu_item
        """
        result = {'children': []}
        result.update(self._menu_item_to_dict(menu_item))

        # If children exist iteratively call _generate_..
        children = getattr(menu_item, self.children_field.name)
        if children:
            for child in children:
                result['children'].append(self._generate_menu_tree(child))
        return result

    @classmethod
    def menu_for(cls, identifier, ident_field_value, objectified=False):
        """
        Returns a dictionary of menu tree

        :param identifier: The unique identifier from which the menu
                has to be chosen
        :param ident_field_value: The value of the field that has to be
                looked up on model with search on ident_field
        :param objectified: The value returned is the active record of
                the menu identified rather than a tree.
        """
        # First pick up the menu through identifier
        try:
            menu, = cls.search([
                ('unique_identifier', '=', identifier),
                ('website', '=', request.nereid_website.id),
            ])

        except ValueError:
            current_app.logger.error("Menu %s could not be identified" %
                                     identifier)
            abort(404)

        # Get the data from the model
        MenuItem = Pool().get(menu.model.model)
        try:
            root_menu_item, = MenuItem.search(
                [(menu.identifier_field.name, '=', ident_field_value)],
                limit=1)
        except ValueError:
            current_app.logger.error("Menu %s could not be identified" %
                                     ident_field_value)
            abort(500)

        if objectified:
            return root_menu_item

        cache_key = key_from_list([
            Transaction().cursor.dbname,
            Transaction().user,
            Transaction().language,
            identifier,
            ident_field_value,
            'nereid.cms.menu.menu_for',
        ])
        rv = cache.get(cache_key)
        if rv is None:
            rv = menu._generate_menu_tree(root_menu_item)
            cache.set(cache_key, rv, 60 * 60)
        return rv

    def on_change_name(self):
        res = {}
        if self.name and not self.unique_identifier:
            res['unique_identifier'] = slugify(self.name)
        return res

    @classmethod
    def context_processor(cls):
        """This function will be called by nereid to update
        the template context. Must return a dictionary that the context
        will be updated with.

        This function is registered with nereid.template.context_processor
        in xml code
        """
        return {'menu_for': cls.menu_for}
Ejemplo n.º 7
0
class AdvancePaymentCondition(ModelSQL, ModelView):
    "Advance Payment Condition"
    __name__ = 'sale.advance_payment.condition'
    _rec_name = 'description'

    _states = {
        'readonly': Eval('sale_state') != 'draft',
        }
    _depends = ['sale_state']

    sale = fields.Many2One('sale.sale', 'Sale', required=True,
        ondelete='CASCADE', select=True,
        states={
            'readonly': ((Eval('sale_state') != 'draft')
                & Bool(Eval('sale'))),
            },
        depends=['sale_state'])
    description = fields.Char(
        "Description", required=True, states=_states, depends=_depends)
    amount = fields.Numeric(
        "Amount",
        digits=(16, Eval('_parent_sale', {}).get('currency_digits', 2)),
        states=_states, depends=_depends)
    account = fields.Many2One(
        'account.account', "Account", required=True,
        domain=[
            ('kind', '=', 'revenue'),
            ('company', '=', Eval('sale_company')),
            ],
        states=_states,
        depends=_depends + ['sale_company'])
    block_supply = fields.Boolean(
        "Block Supply", states=_states, depends=_depends)
    block_shipping = fields.Boolean(
        "Block Shipping", states=_states, depends=_depends)
    invoice_delay = fields.TimeDelta(
        "Invoice Delay", states=_states, depends=_depends)

    invoice_lines = fields.One2Many(
        'account.invoice.line', 'origin', "Invoice Lines", readonly=True)
    completed = fields.Function(fields.Boolean("Completed"), 'get_completed')

    sale_state = fields.Function(fields.Selection(
            'get_sale_states', "Sale State"), 'on_change_with_sale_state')
    sale_company = fields.Function(fields.Many2One(
            'company.company', "Company"), 'on_change_with_sale_company')

    del _states
    del _depends

    @classmethod
    def __setup__(cls):
        super(AdvancePaymentCondition, cls).__setup__()
        cls._order.insert(0, ('amount', 'ASC'))

    @classmethod
    def get_sale_states(cls):
        pool = Pool()
        Sale = pool.get('sale.sale')
        return Sale.fields_get(['state'])['state']['selection']

    @fields.depends('sale', '_parent_sale.state')
    def on_change_with_sale_state(self, name=None):
        if self.sale:
            return self.sale.state

    @fields.depends('sale', '_parent_sale.company')
    def on_change_with_sale_company(self, name=None):
        if self.sale and self.sale.company:
            return self.sale.company.id

    @classmethod
    def copy(cls, conditions, default=None):
        if default is None:
            default = {}
        default['invoice_lines'] = []
        return super(AdvancePaymentCondition, cls).copy(conditions, default)

    def create_invoice(self):
        pool = Pool()
        Invoice = pool.get('account.invoice')

        invoice = self.sale._get_invoice_sale()
        invoice.invoice_date = self.sale.sale_date
        if self.invoice_delay:
            invoice.invoice_date += self.invoice_delay
        invoice.payment_term = None

        invoice_lines = self.get_invoice_advance_payment_lines(invoice)
        if not invoice_lines:
            return None
        invoice.lines = invoice_lines
        invoice.save()

        Invoice.update_taxes([invoice])
        return invoice

    def get_invoice_advance_payment_lines(self, invoice):
        pool = Pool()
        InvoiceLine = pool.get('account.invoice.line')

        advance_amount = self._get_advance_amount()
        advance_amount += self._get_ignored_amount()
        if advance_amount >= self.amount:
            return []

        invoice_line = InvoiceLine()
        invoice_line.invoice = invoice
        invoice_line.company = invoice.company
        invoice_line.type = 'line'
        invoice_line.quantity = 1
        invoice_line.account = self.account
        invoice_line.unit_price = self.amount - advance_amount
        invoice_line.description = self.description
        invoice_line.origin = self
        invoice_line.company = self.sale.company
        # Set taxes
        invoice_line.on_change_account()
        return [invoice_line]

    def _get_advance_amount(self):
        return sum(l.amount for l in self.invoice_lines
            if l.invoice.state != 'cancel')

    def _get_ignored_amount(self):
        skips = {l for i in self.sale.invoices_recreated for l in i.lines}
        return sum(l.amount for l in self.invoice_lines
            if l.invoice.state == 'cancel' and l not in skips)

    def get_completed(self, name):
        advance_amount = 0
        lines_ignored = set(l for i in self.sale.invoices_ignored
            for l in i.lines)
        for l in self.invoice_lines:
            if l.invoice.state == 'paid' or l in lines_ignored:
                advance_amount += l.amount
        return advance_amount >= self.amount
Ejemplo n.º 8
0
class Article(Workflow, ModelSQL, ModelView):
    "CMS Articles"
    __name__ = 'nereid.cms.article'
    _rec_name = 'uri'

    uri = fields.Char('URI', required=True, select=True, translate=True)
    title = fields.Char('Title', required=True, select=True, translate=True)
    content = fields.Text('Content', required=True, translate=True)
    template = fields.Char('Template', required=True)
    active = fields.Boolean('Active', select=True)
    category = fields.Many2One('nereid.cms.article.category',
                               'Category',
                               required=True,
                               select=True)
    image = fields.Many2One('nereid.static.file', 'Image')
    employee = fields.Many2One('company.employee', 'Employee')
    author = fields.Many2One('nereid.user', 'Author')
    published_on = fields.Date('Published On')
    publish_date = fields.Function(fields.Char('Publish Date'),
                                   'get_publish_date')
    sequence = fields.Integer('Sequence', required=True, select=True)
    reference = fields.Reference('Reference', selection='links_get')
    description = fields.Text('Short Description')
    attributes = fields.One2Many('nereid.cms.article.attribute', 'article',
                                 'Attributes')

    # Article can have a banner
    banner = fields.Many2One('nereid.cms.banner', 'Banner')
    state = fields.Selection([('draft', 'Draft'), ('published', 'Published'),
                              ('archived', 'Archived')],
                             'State',
                             required=True,
                             select=True,
                             readonly=True)

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().cursor

        table = TableHandler(cursor, cls, module_name)

        if not table.column_exist('employee'):
            table.column_rename('author', 'employee')

        super(Article, cls).__register__(module_name)

    @classmethod
    def __setup__(cls):
        super(Article, cls).__setup__()
        cls._order.insert(0, ('sequence', 'ASC'))
        cls._transitions |= set((
            ('draft', 'published'),
            ('archived', 'draft'),
            ('published', 'archived'),
        ))
        cls._buttons.update({
            'archive': {
                'invisible': Eval('state') != 'published',
            },
            'publish': {
                'invisible': Eval('state') == 'published',
            }
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('archived')
    def archive(cls, articles):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('published')
    def publish(cls, articles):
        pass

    @staticmethod
    def links_get():
        CMSLink = Pool().get('nereid.cms.link')
        return [('', '')] + [(x.model, x.name) for x in CMSLink.search([])]

    @staticmethod
    def default_active():
        return True

    def on_change_title(self):
        res = {}
        if self.title and not self.uri:
            res['uri'] = slugify(self.title)
        return res

    @staticmethod
    def default_template():
        return 'article.jinja'

    @staticmethod
    def default_employee():
        User = Pool().get('res.user')

        if 'employee' in Transaction().context:
            return Transaction().context['employee']

        user = User(Transaction().user)
        if user.employee:
            return user.employee.id

        if has_request_context() and request.nereid_user.employee:
            return request.nereid_user.employee.id

    @staticmethod
    def default_author():
        if has_request_context():
            return request.nereid_user.id

    @staticmethod
    def default_published_on():
        Date = Pool().get('ir.date')
        return Date.today()

    @classmethod
    @route('/article/<uri>')
    def render(cls, uri):
        """
        Renders the template
        """
        try:
            article, = cls.search([('uri', '=', uri)])
        except ValueError:
            abort(404)
        return render_template(article.template, article=article)

    @classmethod
    @route('/sitemaps/article-index.xml')
    def sitemap_index(cls):
        index = SitemapIndex(cls, [])
        return index.render()

    @classmethod
    @route('/sitemaps/article-<int:page>.xml')
    def sitemap(cls, page):
        sitemap_section = SitemapSection(cls, [], page)
        sitemap_section.changefreq = 'daily'
        return sitemap_section.render()

    @classmethod
    def get_publish_date(cls, records, name):
        """
        Return publish date to render on view
        """
        res = {}
        for record in records:
            res[record.id] = str(record.published_on)
        return res

    def get_absolute_url(self, **kwargs):
        return url_for('nereid.cms.article.render', uri=self.uri, **kwargs)

    @staticmethod
    def default_state():
        if 'published' in Transaction().context:
            return 'published'
        return 'draft'
Ejemplo n.º 9
0
class ArticleCategory(ModelSQL, ModelView):
    "Article Categories"
    __name__ = 'nereid.cms.article.category'
    _rec_name = 'title'

    per_page = 10

    title = fields.Char('Title',
                        size=100,
                        translate=True,
                        required=True,
                        on_change=['title', 'unique_name'],
                        select=True)
    unique_name = fields.Char('Unique Name',
                              required=True,
                              select=True,
                              help='Unique Name is used as the uri.')
    active = fields.Boolean('Active', select=True)
    description = fields.Text('Description', translate=True)
    template = fields.Char('Template', required=True)
    articles = fields.One2Many('nereid.cms.article',
                               'category',
                               'Articles',
                               context={'published': True})

    # Article Category can have a banner
    banner = fields.Many2One('nereid.cms.banner', 'Banner')
    sort_order = fields.Selection([
        ('older_first', 'Older First'),
        ('recent_first', 'Recent First'),
    ], 'Sort Order')
    published_articles = fields.Function(
        fields.One2Many('nereid.cms.article', 'category',
                        'Published Articles'), 'get_published_articles')

    @staticmethod
    def default_sort_order():
        return 'recent_first'

    @staticmethod
    def default_active():
        'Return True'
        return True

    @staticmethod
    def default_template():
        return 'article-category.jinja'

    @classmethod
    def __setup__(cls):
        super(ArticleCategory, cls).__setup__()
        cls._sql_constraints += [
            ('unique_name', 'UNIQUE(unique_name)',
             'The Unique Name of the Category must be unique.'),
        ]

    def on_change_title(self):
        res = {}
        if self.title and not self.unique_name:
            res['unique_name'] = slugify(self.title)
        return res

    @classmethod
    @route('/article-category/<uri>/')
    @route('/article-category/<uri>/<int:page>')
    def render(cls, uri, page=1):
        """
        Renders the category
        """
        Article = Pool().get('nereid.cms.article')

        # Find in cache or load from DB
        try:
            category, = cls.search([('unique_name', '=', uri)])
        except ValueError:
            abort(404)

        order = []
        if category.sort_order == 'recent_first':
            order.append(('write_date', 'DESC'))
        elif category.sort_order == 'older_first':
            order.append(('write_date', 'ASC'))

        articles = Pagination(Article, [('category', '=', category.id)],
                              page,
                              cls.per_page,
                              order=order)
        return render_template(category.template,
                               category=category,
                               articles=articles)

    @classmethod
    def get_article_category(cls, uri, silent=True):
        """Returns the browse record of the article category given by uri
        """
        category = cls.search([('unique_name', '=', uri)], limit=1)
        if not category and not silent:
            raise RuntimeError("Article category %s not found" % uri)
        return category[0] if category else None

    @classmethod
    def context_processor(cls):
        """This function will be called by nereid to update
        the template context. Must return a dictionary that the context
        will be updated with.

        This function is registered with nereid.template.context_processor
        in xml code
        """
        return {'get_article_category': cls.get_article_category}

    @classmethod
    @route('/sitemaps/article-category-index.xml')
    def sitemap_index(cls):
        index = SitemapIndex(cls, [])
        return index.render()

    @classmethod
    @route('/sitemaps/article-category-<int:page>.xml')
    def sitemap(cls, page):
        sitemap_section = SitemapSection(cls, [], page)
        sitemap_section.changefreq = 'daily'
        return sitemap_section.render()

    def get_absolute_url(self, **kwargs):
        return url_for('nereid.cms.article.category.render',
                       uri=self.unique_name,
                       **kwargs)

    def get_published_articles(self, name):
        """
        Get the published articles.
        """
        NereidArticle = Pool().get('nereid.cms.article')

        articles = NereidArticle.search([('state', '=', 'published'),
                                         ('category', '=', self.id)])
        return map(int, articles)
Ejemplo n.º 10
0
class Banner(Workflow, ModelSQL, ModelView):
    """Banner for CMS."""
    __name__ = 'nereid.cms.banner'

    name = fields.Char('Name', required=True, select=True)
    description = fields.Text('Description')
    category = fields.Many2One('nereid.cms.banner.category',
                               'Category',
                               required=True,
                               select=True)
    sequence = fields.Integer('Sequence', select=True)

    # Type related data
    type = fields.Selection([
        ('image', 'Image'),
        ('remote_image', 'Remote Image'),
        ('custom_code', 'Custom Code'),
    ],
                            'Type',
                            required=True)
    file = fields.Many2One('nereid.static.file',
                           'File',
                           states={
                               'required': Equal(Eval('type'), 'image'),
                               'invisible': Not(Equal(Eval('type'), 'image'))
                           })
    remote_image_url = fields.Char('Remote Image URL',
                                   states={
                                       'required':
                                       Equal(Eval('type'), 'remote_image'),
                                       'invisible':
                                       Not(Equal(Eval('type'), 'remote_image'))
                                   })
    custom_code = fields.Text('Custom Code',
                              translate=True,
                              states={
                                  'required':
                                  Equal(Eval('type'), 'custom_code'),
                                  'invisible':
                                  Not(Equal(Eval('type'), 'custom_code'))
                              })

    # Presentation related Data
    height = fields.Integer(
        'Height',
        states={'invisible': Not(In(Eval('type'), ['image', 'remote_image']))})
    width = fields.Integer(
        'Width',
        states={'invisible': Not(In(Eval('type'), ['image', 'remote_image']))})
    alternative_text = fields.Char(
        'Alternative Text',
        translate=True,
        states={'invisible': Not(In(Eval('type'), ['image', 'remote_image']))})
    click_url = fields.Char(
        'Click URL',
        translate=True,
        states={'invisible': Not(In(Eval('type'), ['image', 'remote_image']))})

    state = fields.Selection([('draft', 'Draft'), ('published', 'Published'),
                              ('archived', 'Archived')],
                             'State',
                             required=True,
                             select=True,
                             readonly=True)
    reference = fields.Reference('Reference', selection='links_get')

    @classmethod
    def __setup__(cls):
        super(Banner, cls).__setup__()
        cls._order.insert(0, ('sequence', 'ASC'))
        cls._transitions |= set((
            ('draft', 'published'),
            ('archived', 'published'),
            ('published', 'archived'),
        ))
        cls._buttons.update({
            'archive': {
                'invisible': Eval('state') != 'published',
            },
            'publish': {
                'invisible': Eval('state') == 'published',
            }
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('archived')
    def archive(cls, banners):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('published')
    def publish(cls, banners):
        pass

    def get_html(self):
        """Return the HTML content"""
        StaticFile = Pool().get('nereid.static.file')

        banner = self.read([self], [
            'type', 'click_url', 'file', 'remote_image_url', 'custom_code',
            'height', 'width', 'alternative_text', 'click_url'
        ])[0]

        if banner['type'] == 'image':
            # replace the `file` in the dictionary with the complete url
            # that is required to render the image based on static file
            file = StaticFile(banner['file'])
            banner['file'] = file.url
            image = Template(u'<a href="$click_url">'
                             u'<img src="$file" alt="$alternative_text"'
                             u' width="$width" height="$height"/>'
                             u'</a>')
            return image.substitute(**banner)
        elif banner['type'] == 'remote_image':
            image = Template(
                u'<a href="$click_url">'
                u'<img src="$remote_image_url" alt="$alternative_text"'
                u' width="$width" height="$height"/>'
                u'</a>')
            return image.substitute(**banner)
        elif banner['type'] == 'custom_code':
            return banner['custom_code']

    @staticmethod
    def links_get():
        CMSLink = Pool().get('nereid.cms.link')
        return [('', '')] + [(x.model, x.name) for x in CMSLink.search([])]

    @staticmethod
    def default_type():
        return 'image'

    @staticmethod
    def default_state():
        if 'published' in Transaction().context:
            return 'published'
        return 'draft'
Ejemplo n.º 11
0
class MenuItem(ModelSQL, ModelView):
    "Nereid CMS Menuitem"
    __name__ = 'nereid.cms.menuitem'
    _rec_name = 'unique_name'

    title = fields.Char('Title',
                        required=True,
                        on_change=['title', 'unique_name'],
                        select=True,
                        translate=True)
    unique_name = fields.Char('Unique Name', required=True, select=True)
    link = fields.Char('Link')
    use_url_builder = fields.Boolean('Use URL Builder')
    url_for_build = fields.Many2One(
        'nereid.url_rule',
        'Rule',
        depends=['use_url_builder'],
        states={
            'required': Equal(Bool(Eval('use_url_builder')), True),
            'invisible': Not(Equal(Bool(Eval('use_url_builder')), True)),
        })
    values_to_build = fields.Char(
        'Values',
        depends=['use_url_builder'],
        states={
            'required': Equal(Bool(Eval('use_url_builder')), True),
            'invisible': Not(Equal(Bool(Eval('use_url_builder')), True)),
        })
    full_url = fields.Function(fields.Char('Full URL'), 'get_full_url')
    parent = fields.Many2One(
        'nereid.cms.menuitem',
        'Parent Menuitem',
    )
    child = fields.One2Many('nereid.cms.menuitem',
                            'parent',
                            string='Child Menu Items')
    active = fields.Boolean('Active')
    sequence = fields.Integer('Sequence', required=True, select=True)

    reference = fields.Reference('Reference', selection='links_get')

    def get_full_url(self, name):
        # TODO
        return ''

    @staticmethod
    def links_get():
        CMSLink = Pool().get('nereid.cms.link')
        links = [(x.model, x.name) for x in CMSLink.search([])]
        links.append([None, ''])
        return links

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_values_to_build():
        return '{ }'

    @classmethod
    def __setup__(cls):
        super(MenuItem, cls).__setup__()
        cls._error_messages.update({
            'wrong_recursion':
            'Error ! You can not create recursive menuitems.',
        })
        cls._order.insert(0, ('sequence', 'ASC'))

    @classmethod
    def validate(cls, menus):
        super(MenuItem, cls).validate(menus)
        cls.check_recursion(menus)

    def on_change_title(self):
        res = {}
        if self.title and not self.unique_name:
            res['unique_name'] = slugify(self.title)
        return res

    def get_rec_name(self, name):
        def _name(menuitem):
            if menuitem.parent:
                return _name(menuitem.parent) + ' / ' + menuitem.title
            else:
                return menuitem.title

        return _name(self)
Ejemplo n.º 12
0
class Account(ModelSQL, ModelView):
    'Analytic Account'
    __name__ = 'analytic_account.account'
    name = fields.Char('Name', required=True, translate=True, select=True)
    code = fields.Char('Code', select=True)
    active = fields.Boolean('Active', select=True)
    company = fields.Many2One('company.company', 'Company', required=True)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    type = fields.Selection([
        ('root', 'Root'),
        ('view', 'View'),
        ('normal', 'Normal'),
        ('distribution', 'Distribution'),
    ],
                            'Type',
                            required=True)
    root = fields.Many2One('analytic_account.account',
                           'Root',
                           select=True,
                           domain=[
                               ('company', '=', Eval('company', -1)),
                               ('parent', '=', None),
                               ('type', '=', 'root'),
                           ],
                           states={
                               'invisible': Eval('type') == 'root',
                               'required': Eval('type') != 'root',
                           },
                           depends=['company', 'type'])
    parent = fields.Many2One('analytic_account.account',
                             'Parent',
                             select=True,
                             domain=[
                                 'OR',
                                 ('root', '=', Eval('root', -1)),
                                 ('parent', '=', None),
                             ],
                             states={
                                 'invisible': Eval('type') == 'root',
                                 'required': Eval('type') != 'root',
                             },
                             depends=['root', 'type'])
    childs = fields.One2Many('analytic_account.account',
                             'parent',
                             'Children',
                             states={
                                 'invisible': Eval('id', -1) < 0,
                             },
                             domain=[
                                 ('company', '=', Eval('company', -1)),
                             ],
                             depends=['company'])
    balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 1)),
                       depends=['currency_digits']), 'get_balance')
    credit = fields.Function(
        fields.Numeric('Credit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_credit_debit')
    debit = fields.Function(
        fields.Numeric('Debit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_credit_debit')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('opened', 'Opened'),
        ('closed', 'Closed'),
    ],
                             'State',
                             required=True)
    note = fields.Text('Note')
    display_balance = fields.Selection([
        ('debit-credit', 'Debit - Credit'),
        ('credit-debit', 'Credit - Debit'),
    ],
                                       'Display Balance',
                                       required=True)
    mandatory = fields.Boolean(
        'Mandatory',
        states={
            'invisible': Eval('type') != 'root',
        },
        depends=['type'],
        help="Make this account mandatory when filling documents")
    distributions = fields.One2Many('analytic_account.account.distribution',
                                    'parent',
                                    "Distributions",
                                    states={
                                        'invisible':
                                        Eval('type') != 'distribution',
                                        'required':
                                        Eval('type') == 'distribution',
                                    },
                                    depends=['type'])
    distribution_parents = fields.Many2Many(
        'analytic_account.account.distribution',
        'account',
        'parent',
        "Distribution Parents",
        readonly=True)

    @classmethod
    def __setup__(cls):
        super(Account, cls).__setup__()
        cls._order.insert(0, ('code', 'ASC'))
        cls._order.insert(1, ('name', 'ASC'))
        cls._error_messages.update({
            'invalid_distribution':
            ('The distribution sum of account "%(account)s" '
             'is not 100%.'),
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        super(Account, cls).__register__(module_name)
        table = TableHandler(cls, module_name)

        # Migration from 4.0: remove currency
        table.not_null_action('currency', action='remove')

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_type():
        return 'normal'

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_display_balance():
        return 'credit-debit'

    @staticmethod
    def default_mandatory():
        return False

    @classmethod
    def validate(cls, accounts):
        super(Account, cls).validate(accounts)
        cls.check_recursion(accounts)
        cls.check_recursion(accounts, parent='distribution_parents')
        for account in accounts:
            account.check_distribution()

    def check_distribution(self):
        if self.type != 'distribution':
            return
        if sum((d.ratio for d in self.distributions)) != 1:
            self.raise_user_error('invalid_distribution', {
                'account': self.rec_name,
            })

    @fields.depends('company')
    def on_change_with_currency(self, name=None):
        if self.company:
            return self.company.currency.id

    @fields.depends('company')
    def on_change_with_currency_digits(self, name=None):
        if self.company:
            return self.company.currency.digits
        return 2

    @fields.depends('parent', 'type', '_parent_parent.root',
                    '_parent_parent.type')
    def on_change_parent(self):
        if self.parent and self.type != 'root':
            if self.parent.type == 'root':
                self.root = self.parent
            else:
                self.root = self.parent.root
        else:
            self.root = None

    @classmethod
    def get_balance(cls, accounts, name):
        pool = Pool()
        Line = pool.get('analytic_account.line')
        MoveLine = pool.get('account.move.line')
        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        line = Line.__table__()
        move_line = MoveLine.__table__()

        ids = [a.id for a in accounts]
        childs = cls.search([('parent', 'child_of', ids)])
        all_ids = {}.fromkeys(ids + [c.id for c in childs]).keys()

        id2account = {}
        all_accounts = cls.browse(all_ids)
        for account in all_accounts:
            id2account[account.id] = account

        line_query = Line.query_get(line)
        cursor.execute(
            *table.join(line, 'LEFT', condition=table.id == line.account).join(
                move_line, 'LEFT', condition=move_line.id == line.move_line).
            select(table.id,
                   Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)),
                   where=(table.type != 'view')
                   & table.id.in_(all_ids)
                   & (table.active == True) & line_query,
                   group_by=table.id))
        account_sum = defaultdict(Decimal)
        for account_id, value in cursor.fetchall():
            account_sum.setdefault(account_id, Decimal('0.0'))
            # SQLite uses float for SUM
            if not isinstance(value, Decimal):
                value = Decimal(str(value))
            account_sum[account_id] += value

        balances = {}
        for account in accounts:
            balance = Decimal()
            childs = cls.search([
                ('parent', 'child_of', [account.id]),
            ])
            for child in childs:
                balance += account_sum[child.id]
            if account.display_balance == 'credit-debit' and balance:
                balance *= -1
            balances[account.id] = account.currency.round(balance)
        return balances

    @classmethod
    def get_credit_debit(cls, accounts, names):
        pool = Pool()
        Line = pool.get('analytic_account.line')
        MoveLine = pool.get('account.move.line')
        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        line = Line.__table__()
        move_line = MoveLine.__table__()

        result = {}
        ids = [a.id for a in accounts]
        for name in names:
            if name not in ('credit', 'debit'):
                raise Exception('Bad argument')
            result[name] = {}.fromkeys(ids, Decimal('0.0'))

        id2account = {}
        for account in accounts:
            id2account[account.id] = account

        line_query = Line.query_get(line)
        columns = [table.id]
        for name in names:
            columns.append(Sum(Coalesce(Column(line, name), 0)))
        cursor.execute(
            *table.join(line, 'LEFT', condition=table.id == line.account).join(
                move_line, 'LEFT', condition=move_line.id ==
                line.move_line).select(*columns,
                                       where=(table.type != 'view')
                                       & table.id.in_(ids)
                                       & (table.active == True) & line_query,
                                       group_by=table.id))

        for row in cursor.fetchall():
            account_id = row[0]
            for i, name in enumerate(names, 1):
                value = row[i]
                # SQLite uses float for SUM
                if not isinstance(value, Decimal):
                    value = Decimal(str(value))
                result[name][account_id] += value
        for account in accounts:
            for name in names:
                result[name][account.id] = account.currency.round(
                    result[name][account.id])
        return result

    def get_rec_name(self, name):
        if self.code:
            return self.code + ' - ' + unicode(self.name)
        else:
            return unicode(self.name)

    @classmethod
    def search_rec_name(cls, name, clause):
        accounts = cls.search([('code', ) + tuple(clause[1:])], limit=1)
        if accounts:
            return [('code', ) + tuple(clause[1:])]
        else:
            return [(cls._rec_name, ) + tuple(clause[1:])]

    def distribute(self, amount):
        "Return a list of (account, amount) distribution"
        assert self.type in {'normal', 'distribution'}
        if self.type == 'normal':
            return [(self, amount)]
        else:
            result = []
            remainder = amount
            for distribution in self.distributions:
                account = distribution.account
                ratio = distribution.ratio
                current_amount = self.currency.round(amount * ratio)
                remainder -= current_amount
                result.extend(account.distribute(current_amount))
            if remainder:
                i = 0
                while remainder:
                    account, amount = result[i]
                    rounding = self.currency.rounding.copy_sign(remainder)
                    result[i] = (account, amount - rounding)
                    remainder -= rounding
                    i = (i + 1) % len(result)
            return result