Example #1
0
class ArticleCategory(ModelSQL, ModelView, CMSMenuItemMixin):
    "Article Categories"
    __name__ = 'nereid.cms.article.category'
    _rec_name = 'title'

    title = fields.Char('Title',
                        size=100,
                        translate=True,
                        required=True,
                        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.Many2Many('nereid.cms.category-article',
                                'category',
                                'article',
                                'Article',
                                context={'published': True})

    # Article Category can have a banner
    banner = fields.Many2One('nereid.cms.banner', 'Banner')
    sort_order = fields.Selection([
        ('sequence', 'Sequence'),
        ('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')
    articles_per_page = fields.Integer('Articles per Page', required=True)

    @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.'),
        ]

    @fields.depends('title', 'unique_name')
    def on_change_title(self):
        if self.title and not self.unique_name:
            self.unique_name = slugify(self.title)

    @staticmethod
    def default_articles_per_page():
        return 10

    @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'))
        elif category.sort_order == 'sequence':
            order.append(('sequence', 'ASC'))

        articles = Pagination(Article, [('categories', '=', category.id),
                                        ('state', '=', 'published')],
                              page,
                              category.articles_per_page,
                              order=order)
        return render_template(category.template,
                               category=category,
                               articles=articles)

    @classmethod
    @context_processor('get_article_category')
    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
    @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'),
                                         ('categories', '=', self.id)])
        return map(int, articles)

    def get_children(self, max_depth):
        """
        Return serialized menu_item for current menu_item children
        """
        NereidArticle = Pool().get('nereid.cms.article')

        articles = NereidArticle.search([('state', '=', 'published'),
                                         ('categories', '=', self.id)])
        return [
            article.get_menu_item(max_depth=max_depth - 1)
            for article in articles
        ]

    def serialize(self, purpose=None):
        """
        Article category serialize method
        """
        if purpose == 'atom':
            return {
                'term': self.unique_name,
            }
        elif hasattr(super(ArticleCategory, self), 'serialize'):
            return super(ArticleCategory, self).serialize(purpose=purpose)

    @classmethod
    @route('/article-category/<uri>.atom')
    def atom_feed(cls, uri):
        """
        Returns atom feed for articles published under a particular category.
        """
        try:
            category, = cls.search([
                ('unique_name', '=', uri),
            ], limit=1)
        except ValueError:
            abort(404)

        feed = AtomFeed("Articles by Category %s" % category.unique_name,
                        feed_url=request.url,
                        url=request.host_url)
        for article in category.published_articles:
            feed.add(**article.serialize(purpose='atom'))

        return feed.get_response()
class BalancePartyYear(ModelSQL, ModelView):
    "Turnover and Balances Analytic Account Parties - FiscalYear"
    _name = "ekd.balances.party.year"
    _description =__doc__

    account = fields.Many2One('ekd.balances.party', 'Analytic Account', required=True,  select=2, ondelete="CASCADE")
    fiscalyear = fields.Many2One('ekd.fiscalyear', 'FiscalYear', required=True)
    balance = fields.Numeric('Debit Start', digits=(16, Eval('currency_digits', 2)),)
    debit_01 = fields.Numeric('Debit Turnover 01', digits=(16, Eval('currency_digits', 2)),)
    credit_01 = fields.Numeric('Credit Turnover 01', digits=(16, Eval('currency_digits', 2)),)
    debit_02 = fields.Numeric('Debit Turnover 02', digits=(16, Eval('currency_digits', 2)),)
    credit_02 = fields.Numeric('Credit Turnover 02', digits=(16, Eval('currency_digits', 2)),)
    debit_03 = fields.Numeric('Debit Turnover 03', digits=(16, Eval('currency_digits', 2)),)
    credit_03 = fields.Numeric('Credit Turnover 03', digits=(16, Eval('currency_digits', 2)),)
    debit_04 = fields.Numeric('Debit Turnover 04', digits=(16, Eval('currency_digits', 2)),)
    credit_04 = fields.Numeric('Credit Turnover 04', digits=(16, Eval('currency_digits', 2)),)
    debit_05 = fields.Numeric('Debit Turnover 05', digits=(16, Eval('currency_digits', 2)),)
    credit_05 = fields.Numeric('Credit Turnover 05', digits=(16, Eval('currency_digits', 2)),)
    debit_06 = fields.Numeric('Debit Turnover 06', digits=(16, Eval('currency_digits', 2)),)
    credit_06 = fields.Numeric('Credit Turnover 06', digits=(16, Eval('currency_digits', 2)),)
    debit_07 = fields.Numeric('Debit Turnover 07', digits=(16, Eval('currency_digits', 2)),)
    credit_07 = fields.Numeric('Credit Turnover 07', digits=(16, Eval('currency_digits', 2)),)
    debit_08 = fields.Numeric('Debit Turnover 08', digits=(16, Eval('currency_digits', 2)),)
    credit_08 = fields.Numeric('Credit Turnover 08', digits=(16, Eval('currency_digits', 2)),)
    debit_09 = fields.Numeric('Debit Turnover 09', digits=(16, Eval('currency_digits', 2)),)
    credit_09 = fields.Numeric('Credit Turnover 09', digits=(16, Eval('currency_digits', 2)),)
    debit_10 = fields.Numeric('Debit Turnover 10', digits=(16, Eval('currency_digits', 2)),)
    credit_10 = fields.Numeric('Credit Turnover 10', digits=(16, Eval('currency_digits', 2)),)
    debit_11 = fields.Numeric('Debit Turnover 11', digits=(16, Eval('currency_digits', 2)),)
    credit_11 = fields.Numeric('Credit Turnover 11', digits=(16, Eval('currency_digits', 2)),)
    debit_12 = fields.Numeric('Debit Turnover 12', digits=(16, Eval('currency_digits', 2)),)
    credit_12 = fields.Numeric('Credit Turnover 12', digits=(16, Eval('currency_digits', 2)),)
    dt_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Ref entry debit lines'), 'get_entries_field')
    ct_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Ref entry credit lines'), 'get_entries_field')
    parent = fields.Many2One('ekd.balances.party.year','ID Parent balance')
    transfer = fields.Many2One('ekd.balances.party.year','ID Transfer balance')
    state = fields.Selection([
                 ('draft','Draft'),
                 ('open','Open'),
                 ('done','Closed'),
                 ('deleted','Deleted')
                 ], 'State', required=True)
    deleted = fields.Boolean('Flag Deleting')
    active = fields.Boolean('Active')

    def __init__(self):
        super(BalancePartyYear, self).__init__()

    def init(self, module_name):
        cursor = Transaction().cursor
        super(BalancePartyYear, self).init(module_name)
        table = TableHandler(cursor, self, module_name)
        # Проверяем счетчик
        cursor.execute("SELECT last_value, increment_by FROM %s"%table.sequence_name)
        last_value, increment_by = cursor.fetchall()[0]

        # Устанавливаем счетчик
        if str(last_value)[len(str(last_value))-1] != str(_ID_TABLES_BALANCES_PERIOD[self._table]):
            cursor.execute("SELECT setval('"+table.sequence_name+"', %s, true)"%_ID_TABLES_BALANCES_PERIOD[self._table])
        if increment_by != 10:
            cursor.execute("ALTER SEQUENCE "+table.sequence_name+" INCREMENT 10")

    def default_state(self):
        return Transaction().context.get('state') or 'draft'

    def default_active(self):
        return True

    def get_currency_digits(self, ids, name):
        res = {}.fromkeys(ids, 2)
        for line in self.browse(ids):
            res.setdefault('currency_digits', {})
            res['currency_digits'][line.id] = line.account.currency_digits or 2
        return res
Example #3
0
class Glasgow(ModelSQL, ModelView):
    'Glasgow Coma Scale'
    __name__ = 'gnuhealth.icu.glasgow'

    name = fields.Many2One('gnuhealth.inpatient.registration',
                           'Registration Code',
                           required=True)

    evaluation_date = fields.DateTime('Date',
                                      help="Date / Time",
                                      required=True)

    glasgow = fields.Integer(
        'Glasgow',
        on_change_with=['glasgow_verbal', 'glasgow_motor', 'glasgow_eyes'],
        help='Level of Consciousness - on Glasgow Coma Scale :  < 9 severe -'
        ' 9-12 Moderate, > 13 minor')
    glasgow_eyes = fields.Selection([
        ('1', '1 : Does not Open Eyes'),
        ('2', '2 : Opens eyes in response to painful stimuli'),
        ('3', '3 : Opens eyes in response to voice'),
        ('4', '4 : Opens eyes spontaneously'),
    ],
                                    'Eyes',
                                    sort=False)
    glasgow_verbal = fields.Selection([
        ('1', '1 : Makes no sounds'),
        ('2', '2 : Incomprehensible sounds'),
        ('3', '3 : Utters inappropriate words'),
        ('4', '4 : Confused, disoriented'),
        ('5', '5 : Oriented, converses normally'),
    ],
                                      'Verbal',
                                      sort=False)
    glasgow_motor = fields.Selection([
        ('1', '1 : Makes no movement'),
        ('2', '2 : Extension to painful stimuli - decerebrate response -'),
        ('3', '3 : Abnormal flexion to painful stimuli \
            (decorticate response)'),
        ('4', '4 : Flexion / Withdrawal to painful stimuli'),
        ('5', '5 : localizes painful stimuli'),
        ('6', '6 : Obeys commands'),
    ],
                                     'Motor',
                                     sort=False)

    @staticmethod
    def default_glasgow_eyes():
        return '4'

    @staticmethod
    def default_glasgow_verbal():
        return '5'

    @staticmethod
    def default_glasgow_motor():
        return '6'

    @staticmethod
    def default_glasgow():
        return 15

    # Default evaluation date
    @staticmethod
    def default_evaluation_date():
        return datetime.now()

    def on_change_with_glasgow(self):
        return int(self.glasgow_motor) + int(self.glasgow_eyes) + \
            int(self.glasgow_verbal)

    # Return the Glasgow Score with each component
    def get_rec_name(self, name):
        if self.name:
            res = str(self.glasgow) + ': ' + 'E' + self.glasgow_eyes + ' V' + \
                self.glasgow_verbal + ' M' + self.glasgow_motor
        return res
class Sale:
    'Sale'
    __name__ = 'sale.sale'

    # Readonly because the wizard should be the one adding payment gateways as
    # it provides a more cusomizable UX than directly adding a record.
    # For example, taking CC numbers.
    payments = fields.One2Many(
        'sale.payment', 'sale', 'Payments', readonly=True,
    )
    sorted_payments = fields.Function(
        fields.One2Many('sale.payment', None, 'Payments'),
        'get_sorted_payments'
    )

    # Sale must be able to define when it should authorize and capture the
    # payments.
    payment_authorize_on = fields.Selection(
        'get_authorize_options', 'Authorize payments', required=True,
        states=READONLY_STATES, depends=DEPENDS
    )
    payment_capture_on = fields.Selection(
        'get_capture_options', 'Capture payments', required=True,
        states=READONLY_STATES, depends=DEPENDS
    )

    gateway_transactions = fields.Function(
        fields.One2Many(
            'payment_gateway.transaction', None, 'Gateway Transactions',
        ), "get_gateway_transactions"
    )
    payment_total = fields.Function(
        fields.Numeric(
            'Total Payment', digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits'],
            help="Total value of payments"
        ), 'get_payment',
    )
    payment_collected = fields.Function(
        fields.Numeric(
            'Payment Collected', digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits'],
            help="Total value of payments collected"
        ), 'get_payment',
    )
    payment_available = fields.Function(
        fields.Numeric(
            'Payment Remaining', digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits'],
            help="Total value which is neither authorize nor captured"
        ), 'get_payment',
    )
    payment_authorized = fields.Function(
        fields.Numeric(
            'Payment Authorized', digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits'],
            help="Amount authorized to be catured"
        ), 'get_payment',
    )
    payment_captured = fields.Function(
        fields.Numeric(
            'Payment Captured', digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits'],
            help="Amount already captured"
        ), 'get_payment',
    )
    payment_processing_state = fields.Selection([
        (None, 'None'),
        ('waiting_for_auth', 'Waiting For Authorization'),
        ('waiting_for_capture', 'Waiting For Capture'),
    ], "Payment Processing State", select=True, readonly=True)

    @staticmethod
    def default_payment_processing_state():
        return None

    @classmethod
    def __setup__(cls):
        super(Sale, cls).__setup__()
        cls._buttons.update({
            'add_payment': {
                'invisible': Eval('state').in_(['cancel', 'draft']),
            },
            'auth_capture': {
                'invisible': Eval('state').in_(['cancel', 'draft', 'done']),
            },
        })
        cls._error_messages.update({
            'insufficient_amount_to_authorize':
                "Insufficient amount remaining in payment\n"
                "Amount to authorize: %s\n"
                "Amount remaining: %s\n"
                "Payments: %s",
            'insufficient_amount_to_capture':
                "Insufficient amount remaining in payment\n"
                "Amount to capture: %s\n"
                "Amount remaining: %s\n"
                "Payments: %s",
            "auth_before_capture":
                "Payment authorization must happen before capture",
            "sale_payments_waiting": "Sale Payments are %s",
        })

    @classmethod
    def validate(cls, sales):
        super(Sale, cls).validate(sales)

        for sale in sales:
            sale.validate_payment_combination()

    def validate_payment_combination(self):
        if self.payment_authorize_on == 'sale_process' and \
                self.payment_capture_on == 'sale_confirm':
            self.raise_user_error("auth_before_capture")

    @classmethod
    def get_authorize_options(cls):
        """Return all the options from sale configuration.
        """
        SaleConfiguration = Pool().get('sale.configuration')

        return SaleConfiguration.get_authorize_options()

    @classmethod
    def get_capture_options(cls):
        """Return all the options from sale configuration.
        """
        SaleConfiguration = Pool().get('sale.configuration')

        return SaleConfiguration.get_capture_options()

    @staticmethod
    def default_payment_authorize_on():
        SaleConfiguration = Pool().get('sale.configuration')

        return SaleConfiguration(1).payment_authorize_on

    @staticmethod
    def default_payment_capture_on():
        SaleConfiguration = Pool().get('sale.configuration')

        return SaleConfiguration(1).payment_capture_on

    @classmethod
    def get_payment_method_priority(cls):
        """Priority order for payment methods. Downstream modules can override
        this method to change the method priority.
        """
        return ('manual', 'credit_card')

    def get_gateway_transactions(self, name):
        GatewayTransaction = Pool().get('payment_gateway.transaction')

        return map(
            int, GatewayTransaction.search(
                [('sale_payment', 'in', map(int, self.payments))]
            )
        )

    def get_payment(self, name):
        """Return amount from payments.
        """
        Payment = Pool().get('sale.payment')

        payments = Payment.search([('sale', '=', self.id)])

        if name == 'payment_total':
            return Decimal(sum([payment.amount for payment in payments]))

        elif name == 'payment_available':
            return Decimal(
                sum([payment.amount_available for payment in payments])
            )

        elif name == 'payment_captured':
            return Decimal(sum(
                [payment.amount_captured for payment in payments]
            ))

        elif name == 'payment_authorized':
            return Decimal(sum(
                [payment.amount_authorized for payment in payments]
            ))

        elif name == 'payment_collected':
            return self.payment_total - self.payment_available

    @classmethod
    @ModelView.button_action('sale_payment_gateway.wizard_add_payment')
    def add_payment(cls, sales):
        pass

    def get_sorted_payments(self, name=None):
        """
        Return the payments in the order they should be consumed
        """
        payment_method_priority = self.get_payment_method_priority()
        return map(int, sorted(
            self.payments,
            key=lambda t: payment_method_priority.index(t.method)
        ))

    def _raise_sale_payments_waiting(self):
        Sale = Pool().get('sale.sale')

        self.raise_user_error(
            "sale_payments_waiting", (
                dict(Sale.payment_processing_state.selection).get(
                    self.payment_processing_state
                ),
            )
        )

    def authorize_payments(self, amount, description="Payment from sale"):
        """
        Authorize sale payments. It actually creates payment transactions
        corresponding to sale payments and set the payment processing state to
        `waiting to auth`.
        """
        if self.payment_processing_state:
            self._raise_sale_payments_waiting()

        if amount > self.payment_available:
            self.raise_user_error(
                "insufficient_amount_to_authorize", error_args=(
                    amount,
                    self.payment_available,
                    len(self.payments),
                )
            )

        transactions = []
        for payment in self.sorted_payments:
            if not amount:
                break

            if not payment.amount_available or payment.method == "manual":
                # * if no amount available, continue to next.
                # * manual payment need not to be authorized.
                continue

            # The amount to authorize is the amount_available if the
            # amount_available is less than the amount we seek.
            authorize_amount = min(amount, payment.amount_available)

            payment_transaction = payment._create_payment_transaction(
                authorize_amount, description
            )
            payment_transaction.save()

            amount -= authorize_amount

            transactions.append(payment_transaction)

        self.payment_processing_state = "waiting_for_auth"
        self.save()

        return transactions

    def capture_payments(self, amount, description="Payment from sale"):
        """Capture sale payments.

        * If existing authorizations exist, capture them
        * If not, capture available payments directly
        """
        if self.payment_processing_state:
            self._raise_sale_payments_waiting()

        if amount > (self.payment_available + self.payment_authorized):
            self.raise_user_error(
                "insufficient_amount_to_capture", error_args=(
                    amount,
                    self.payment_available,
                    len(self.payments),
                )
            )

        transactions = []
        authorized_transactions = filter(
            lambda transaction: transaction.state == 'authorized',
            self.gateway_transactions
        )
        for transaction in authorized_transactions:
            if not amount:
                break       # pragma: no cover

            capture_amount = min(amount, transaction.amount)

            # Write the new amount of the transaction as the amount
            # required to be captured
            transaction.amount = capture_amount
            transaction.save()

            amount -= capture_amount

            transactions.append(transaction)

        for payment in self.sorted_payments:
            if not amount:
                break

            if not payment.amount_available:
                continue

            # The amount to capture is the amount_available if the
            # amount_available is less than the amount we seek.
            authorize_amount = min(amount, payment.amount_available)

            payment_transaction = payment._create_payment_transaction(
                authorize_amount, description
            )
            payment_transaction.save()

            amount -= authorize_amount

            transactions.append(payment_transaction)

        self.payment_processing_state = "waiting_for_capture"
        self.save()

        return transactions

    @classmethod
    def auth_capture(cls, sales):
        """
        A button triggered version of authorizing or capturing payment directly
        from an order.
        """
        for sale in sales:
            if sale.state == 'confirmed':
                sale.handle_payment_on_confirm()
            elif sale.state == 'processing':
                sale.handle_payment_on_process()
            sale.process_pending_payments()

    def handle_payment_on_confirm(self):
        if self.payment_capture_on == 'sale_confirm':
            self.capture_payments(
                self.total_amount - self.payment_captured
            )

        elif self.payment_authorize_on == 'sale_confirm':
            self.authorize_payments(
                self.total_amount - self.payment_authorized
            )

    def handle_payment_on_process(self):
        if self.payment_capture_on == 'sale_process':
            self.capture_payments(
                self.total_amount - self.payment_captured
            )

        elif self.payment_authorize_on == 'sale_process':
            self.authorize_payments(
                self.total_amount - self.payment_authorized
            )

    def settle_manual_payments(self):
        """
        Manual payments should be settled when the order is processed. This is
        separated into a different method so downstream modules can change this
        behavior to adapt to different workflows
        """
        for payment in self.payments:
            if payment.amount_available and payment.method == "manual" and \
                    not payment.payment_transactions:
                payment_transaction = payment._create_payment_transaction(
                    payment.amount_available,
                    'Post manual payments on Processing Order',
                )
                payment_transaction.save()
                payment.capture()

    @classmethod
    @ModelView.button
    @Workflow.transition('confirmed')
    def confirm(cls, sales):
        super(Sale, cls).confirm(sales)

        for sale in sales:
            sale.handle_payment_on_confirm()

    @classmethod
    def process(cls, sales):
        for sale in sales:
            if sale.state != 'confirmed':
                continue
            sale.handle_payment_on_process()
            sale.settle_manual_payments()
        super(Sale, cls).process(sales)

    def _pay_using_credit_card(self, gateway, credit_card, amount):
        '''
        Complete using the given credit card and finish the transaction.
        :param gateway: Active record of the payment gateway to process card
        :param credit_card: A dictionary with either of the following
                            information sets:
                            * owner: name of the owner (unicode)
                            * number: number of the credit card
                            * expiry_month: expiry month (int or string)
                            * expiry_year: year as string
                            * cvv: the cvv number
                            In future this method will accept track1 and track2
                            as valid information.
        :param amount: Decimal amount to charge the card for
        '''
        TransactionUseCardWizard = Pool().get(
            'payment_gateway.transaction.use_card', type='wizard'
        )
        PaymentTransaction = Pool().get('payment_gateway.transaction')

        # Manual card based operation
        payment_transaction = PaymentTransaction(
            party=self.party,
            address=self.invoice_address,
            amount=amount,
            currency=self.currency,
            gateway=gateway,
            sale=self,
        )
        payment_transaction.save()

        use_card_wiz = TransactionUseCardWizard(
            TransactionUseCardWizard.create()[0]        # Wizard session
        )
        use_card_wiz.card_info.owner = credit_card['owner']
        use_card_wiz.card_info.number = credit_card['number']
        use_card_wiz.card_info.expiry_month = credit_card['expiry_month']
        use_card_wiz.card_info.expiry_year = credit_card['expiry_year']
        use_card_wiz.card_info.csc = credit_card['cvv']

        with Transaction().set_context(active_id=payment_transaction.id):
            use_card_wiz.transition_capture()

    def _pay_using_profile(self, payment_profile, amount):
        '''
        Complete the Checkout using a payment_profile. Only available to the
        registered users of the website.
        :param payment_profile: Active record of payment profile
        :param amount: Decimal amount to charge the card for
        '''
        PaymentTransaction = Pool().get('payment_gateway.transaction')

        if payment_profile.party != self.party:
            self.raise_user_error(
                "Payment profile'd owner is %s, but the customer is %s" % (
                    payment_profile.party.name,
                    self.party.name,
                )
            )

        payment_transaction = PaymentTransaction(
            party=self.party,
            address=self.invoice_address,
            payment_profile=payment_profile,
            amount=amount,
            currency=self.currency,
            gateway=payment_profile.gateway,
            sale=self,
        )
        payment_transaction.save()

        PaymentTransaction.capture([payment_transaction])

    def process_pending_payments(self):
        """Process waiting payments for corresponding sale.
        """
        PaymentTransaction = Pool().get('payment_gateway.transaction')

        if self.payment_processing_state == "waiting_for_auth":
            for payment in self.sorted_payments:
                payment.authorize()
            self.payment_processing_state = None

        elif self.payment_processing_state == "waiting_for_capture":
            # Transactions waiting for capture.
            txns = PaymentTransaction.search([
                ('sale_payment.sale', '=', self.id),
            ])

            # Settle authorized transactions
            PaymentTransaction.settle(filter(
                lambda txn: txn.state == 'authorized', txns
            ))

            # Capture other transactions
            PaymentTransaction.capture(filter(
                lambda txn: txn.state == "draft", txns
            ))

            self.payment_processing_state = None
        else:
            # Weird! Why was I called if there is nothing to do
            return
        self.save()

    @classmethod
    def process_all_pending_payments(cls):
        """Cron method authorizes waiting payments.
        """
        User = Pool().get('res.user')

        user = User(Transaction().user)
        if not (Transaction().context.get('company') or user.company):
            # Processing payments without user's company and company in
            # context is not possible at all. Skip the execution.
            return

        sales = cls.search([
            ('payment_processing_state', '!=', None)
        ])

        for sale in sales:
            sale.process_pending_payments()

    @classmethod
    def copy(cls, records, default=None):
        """
        Duplicating records
        """
        if default is None:
            default = {}

        default['payment_processing_state'] = None
        default['payments'] = []

        return super(Sale, cls).copy(records, default)
class BalanceAnalyticParty(ModelSQL, ModelView):
    "Turnover and Balances Analytic Account"
    _name = "ekd.balances.party"
    _description =__doc__
    _rec_name = 'name_model'

    company = fields.Function(fields.Many2One('company.company', 'Company'), 'get_company')
    account = fields.Many2One('ekd.account', 'Account', required=True,  select=2,
                        order_field="account.code",
                        domain=[
                            ('company','=',Eval('company'))
                        ], depends=['company'])
    type_balance = fields.Function(fields.Char('Type Balance'), 'get_account_type')
    level = fields.Selection(_LEVEL_ANALYTIC, 'Level analityc', required=True)
    model_ref = fields.Reference('Analytic', selection='model_ref_get', select=2)
    name_model = fields.Function(fields.Char('Type', ), 'name_model_get')
    name_ref = fields.Function(fields.Char('Analytic Account', ), 'name_model_get')
    parent = fields.Many2One('ekd.balances.party', 'Parent Analytic')
    amount_periods = fields.One2Many('ekd.balances.party.period', 'account', 'Balances and Turnover (Full)')
    amount_years = fields.One2Many('ekd.balances.party.year', 'account', 'Balances and Turnover (Full)')
    childs = fields.One2Many('ekd.balances.party', 'parent', 'Children')
    balance = fields.Function(fields.Numeric('Start Balance',
                digits=(16, Eval('currency_digits', 2))), 'get_balance_period')
    balance_dt = fields.Function(fields.Numeric('Debit Start',
                digits=(16, Eval('currency_digits', 2))), 'get_balance_period')
    balance_ct = fields.Function(fields.Numeric('Credit Start',
                digits=(16, Eval('currency_digits', 2))), 'get_balance_period')
    debit = fields.Function(fields.Numeric('Debit Turnover',
                digits=(16, Eval('currency_digits', 2)),), 'get_balance_period')
    credit = fields.Function(fields.Numeric('Credit Turnover',
                digits=(16, Eval('currency_digits', 2)),), 'get_balance_period')
    balance_end = fields.Function(fields.Numeric('End Balance',
                digits=(16, Eval('currency_digits', 2))), 'get_balance_period')
    balance_dt_end = fields.Function(fields.Numeric('Debit End',
                digits=(16, Eval('currency_digits', 2))), 'get_balance_period')
    balance_ct_end = fields.Function(fields.Numeric('Credit End',
                digits=(16, Eval('currency_digits', 2))), 'get_balance_period')
    turnover_debit = fields.Function(fields.One2Many('ekd.account.move.line',
                None, 'Entries'), 'get_entry')
    turnover_credit = fields.Function(fields.One2Many('ekd.account.move.line',
                None,'Entries'), 'get_entry')
    currency_digits = fields.Function(fields.Integer('Currency Digits'), 'get_currency_digits')
    state = fields.Selection([
                 ('draft','Draft'),
                 ('open','Open'),
                 ('done','Closed'),
                 ('deleted','Deleted')
                 ], 'State', required=True)
    deleted = fields.Boolean('Flag Deleting')
    active = fields.Boolean('Active')

    def __init__(self):
        super(BalanceAnalyticParty, self).__init__()

        self._order.insert(0, ('account', 'ASC'))
        self._order.insert(1, ('level', 'ASC'))
        self._order.insert(2, ('model_ref', 'ASC'))

        self._sql_constraints += [
                ('balance_account_uniq', 'UNIQUE(account,level,model_ref, parent)',\
                 'account, level, model_ref, parent - must be unique per balance!'),
                 ]

    def init(self, module_name):
        cursor = Transaction().cursor
        super(BalanceAnalyticParty, self).init(module_name)
        table = TableHandler(cursor, self, module_name)
        # Проверяем счетчик
        cursor.execute("SELECT last_value, increment_by FROM %s"%table.sequence_name)
        last_value, increment_by = cursor.fetchall()[0]

        # Устанавливаем счетчик
        if str(last_value)[len(str(last_value))-1] != str(_ID_TABLES_BALANCES[self._table]):
            cursor.execute("SELECT setval('"+table.sequence_name+"', %s, true)"%_ID_TABLES_BALANCES[self._table])
        if increment_by != 10:
            cursor.execute("ALTER SEQUENCE "+table.sequence_name+" INCREMENT 10")

    def default_state(self):
        return Transaction().context.get('state') or 'draft'

    def default_currency(self):
        return Transaction().context.get('currency')

    def default_active(self):
        return True

    def model_ref_get(self):
        dictions_obj = self.pool.get('ir.dictions')
        res = []
        diction_ids = dictions_obj.search([
                    ('model', '=', 'ekd.account.level_analytic'),
                    ('pole', '=', 'type_analytic'),
                    ])
        for diction in dictions_obj.browse(diction_ids):
            res.append([diction.key, diction.value])
        return res

    def get_company(self, ids, name):
        res={}
        context = Transaction().context
        res = {}.fromkeys(ids, context.get('company'))
        return res

    def get_account_type(self, ids, name):
        if name not in ('account_type', 'account_kind', 'account_kind_analytic'):
            raise Exception('Invalid name Balances Finance')
        res = {}
        for line in self.browse(ids):
           if line.account:
            if name == 'account_type':
                res[line.id] = line.account.type.name
            elif name == 'account_kind':
                res[line.id] = line.account.kind
            elif name == 'account_kind_analytic':
                res[line.id] = line.account.kind_analytic
            else:
                res[line.id] = line.account.type_balance
        return res

    def get_entry(self, ids, names):
        move_line = self.pool.get('ekd.account.move.line')
        move_line_analytic_dt = self.pool.get('ekd.account.move.line.analytic_dt')
        move_line_analytic_ct = self.pool.get('ekd.account.move.line.analytic_ct')
        res = {}
        for balance in self.browse(ids):
            for name in names:
                res.setdefault(name, {})
                if name == 'turnover_debit':
                    line_analytic_dt = move_line_analytic_dt.search([('ref_analytic','=', balance.id)])
                    res[name][balance.id] = [ x.get('move_line') for x in move_line_analytic_dt.read(line_analytic_dt, ['move_line']) ]
                elif name == 'turnover_credit':
                    line_analytic_ct = move_line_analytic_ct.search([('ref_analytic','=', balance.id)])
                    res[name][balance.id] = [ x.get('move_line') for x in move_line_analytic_ct.read(line_analytic_ct, ['move_line']) ]

        return res

    def name_model_get(self, ids, names):
        group_model={}
        group_name_model={}
        res={}
        res['name_model']={}.fromkeys(ids, False)
        res['name_ref']={}.fromkeys(ids, False)
        tmp_res={}
        for line in self.browse(ids):
            res['name_model'][line.id] = line.model_ref
            if line.model_ref not in tmp_res.keys():
                tmp_res[line.model_ref] = [line.id,]
            else:
                tmp_res[line.model_ref].append(line.id)
            ref_model, ref_id = line.model_ref.split(',',1)
            if ref_id == '0':
                continue
            if ref_model not in group_model.keys():
                group_model[ref_model] = [int(ref_id),]
            else:
                group_model[ref_model].append(int(ref_id))
        ir_model_obj = self.pool.get('ir.model')
        search_model_ids = ir_model_obj.search([('model','in',group_model.keys())])
        for ir_model_id in ir_model_obj.browse(search_model_ids):
            group_name_model[ir_model_id.model] = ir_model_id.rec_name
        for model in group_model.keys():
            model_obj = self.pool.get(model)
            for model_line in model_obj.browse(group_model[model]):
                rec_id = tmp_res['%s,%s'%(model,str(model_line.id))]
                if isinstance(rec_id, (int,long)):
                    res['name_model'][rec_id] = group_name_model[model]
                    res['name_ref'][rec_id] = model_line.rec_name
                else:
                    for rec_id_ in rec_id: 
                        res['name_model'][rec_id_] = group_name_model[model]
                        res['name_ref'][rec_id_ ] = model_line.rec_name
        return res

    def get_account_type(self, ids, name):
        if name not in ('account_type', 'account_kind', 'type_balance'):
            raise Exception('Invalid name')
        res = {}
        for line in self.browse(ids):
           if line.account:
            if name == 'account_type':
                res[line.id] = line.account.type.name
            elif name == 'account_kind':
                res[line.id] = line.account.kind_analytic
            else:
                res[line.id] = line.account.type_balance
        return res

    def get_currency_digits(self, ids, name):
        res = {}.fromkeys(ids, 2)
        for line in self.browse(ids):
            if line.account.currency:
                res[line.id] = line.account.currency.digits or 2
            elif line.account.company:
                res[line.id] = line.account.company.currency.digits or 2
        return res

    def get_balance_period(self, ids, names):
        if not ids:
            return {}
        res={}
        fiscalyear_obj = self.pool.get('ekd.fiscalyear')
        type_balance = self.browse(ids[0]).account.type_balance
        period_obj = self.pool.get('ekd.period')
        context = Transaction().context
        if context.get('current_period'):
            period_id = period_obj.browse(context.get('current_period'))
            current_period = context.get('current_period')
        elif context.get('current_date'):
            current_period = period_obj.search([
                    ('company','=',context.get('company')),
                    ('start_date','<=',context.get('current_date')),
                    ('end_date','>=',context.get('current_date')),
                    ], limit=1)
        cr = Transaction().cursor
        cr.execute('SELECT id, account, balance_dt, balance_ct, '\
                    'debit, credit, '\
                    ' balance_dt-balance_ct+debit-credit as balance_end, '\
                    ' balance_dt+debit as balance_dt_end, '\
                    ' balance_ct+credit as balance_ct_end '\
                    'FROM ekd_balances_party_period '\
                    'WHERE period=%s AND account in ('%(current_period)+','.join(map(str,ids))+')')
        for amount_id, account, balance_dt, balance_ct,\
            debit, credit, balance_end,\
            balance_dt_end, balance_ct_end in cr.fetchall():
            # SQLite uses float for SUM
            if not isinstance(balance_dt, Decimal):
                balance_dt = Decimal(str(balance_dt))
            if not isinstance(balance_ct, Decimal):
                balance_ct = Decimal(str(balance_ct))
            if not isinstance(balance_dt_end, Decimal):
                balance_dt = Decimal(str(balance_dt_end))
            if not isinstance(balance_ct_end, Decimal):
                balance_ct = Decimal(str(balance_ct_end))
            if not isinstance(debit, Decimal):
                debit = Decimal(str(debit))
            if not isinstance(credit, Decimal):
                credit = Decimal(str(credit))
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(account, Decimal('0.0'))
                amount_balance= Decimal('0.0')
                if name == 'balance_dt_end':
                    if type_balance == 'active':
                        res[name][account] = balance_end
                    #elif type_balance == 'passive':
                    #    res[name][account] = balance_end
                    elif type_balance == 'both':
                        if balance_end > 0:
                            res[name][account] = balance_end
                if name == 'balance_ct_end':
                    if type_balance == 'passive':
                        res[name][account] = -balance_end
                    elif type_balance == 'both':
                        if balance_end < 0:
                            res[name][account] = -balance_end
                elif name == 'balance_end':
                    res[name][account] = balance_end
                elif name == 'balance':
                    res[name][account] = balance_dt-balance_ct
                elif name == 'debit':
                    res[name][account] = debit
                elif name == 'credit':
                    res[name][account] = credit
        return res

    # This Function Test Only!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    def get_balance_year(self, ids, names):
        if not ids:
            return {}
        res={}
        fiscalyear_obj = self.pool.get('ekd.fiscalyear')
        period_obj = self.pool.get('ekd.period')
        context = Transaction().context
        if context.get('current_period'):
            period_id = period_object.browse(context.get('current_period'))
            start_month = period_id.start_date.strftime('%m')
            end_month = period_id.end_date.strftime('%m')
            fiscalyear = period_id.fiscalyear.id
            if start_month == end_month:
                current_period = context.get('current_period').strftime('%m')
            else:
                begin_period = False
                for month in _MOUNTH:
                    if start_month == month:
                        current_period.append(month)
                        begin_period = True
                    elif begin_period:
                        current_period.append(month)
                    elif end_month == month:
                        current_period.append(month)
                        break
        else:
            if context.get('current_fiscalyear'):
                fiscalyear = context.get('current_fiscalyear')
            else:
                fiscalyear = fiscalyear_obj.search([
                                ('company','=',context.get('company')),
                                ('state','=','current')
                                ], limit=1)
            current_period = datetime.datetime.now().strftime('%m')

        if isinstance(current_period, list):
            field_debit = []
            field_credit = []
            for month in current_period:
                field_debit.append("debit_%s"%(month))
                field_credit.append("credit_%s"%(month))
            field_debits = []
            field_credits = []
            for month in _MOUNTH:
                if month == start_month:
                    break
                field_debits.append("debit_%s"%(month))
                field_credits.append("credit_%s"%(month))
        else:
            field_debit = "debit_%s"%(current_period)
            field_credit = "credit_%s"%(current_period)
            field_debits = []
            field_credits = []
            for month in _MOUNTH:
                if month == current_period:
                    break
                field_debits.append("debit_%s"%(month))
                field_credits.append("credit_%s"%(month))

        cr = Transaction().cursor
        if isinstance(current_period, list):
            cr.execute('SELECT id, balance_dt+'+'+'.join(field_debits)+','\
                    'balance_ct+'+'+'.join(field_credits)+','\
                    '+'.join(field_debit)+' as debit,'\
                    '+'.join(field_credit)+' as credit'\
                    'FROM ekd_balances_party_period'\
                    'WHERE fiscalyear=%s AND account in ('+','.join(map(str,ids))+')'%(field_debit,field_credit,fiscalyear))
        else:
            cr.execute('SELECT id, balance_dt+'+'+'.join(field_debits)+','\
                    'balance_ct+'+'+'.join(field_credits)+','\
                    '%s, %s'\
                    'FROM ekd_balances_party_period'\
                    'WHERE fiscalyear=%s AND account in ('+','.join(map(str,ids))+')'%(field_debit,field_credit,fiscalyear))

        for id, balance_dt, balance_ct, debit, credit in cr.fetchall():
            # SQLite uses float for SUM
            if not isinstance(balance_dt, Decimal):
                balance_dt = Decimal(str(balance_dt))
            if not isinstance(balance_ct, Decimal):
                balance_ct = Decimal(str(balance_ct))
            if not isinstance(debit, Decimal):
                debit = Decimal(str(debit))
            if not isinstance(credit, Decimal):
                credit = Decimal(str(credit))
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(balance.id, Decimal('0.0'))
                amount_balance= Decimal('0.0')
                if not balance.amount:
                    continue
                if name == 'balance_dt_end':
                    res[name][balance.id] = balance_end
                elif name == 'balance_ct_end':
                    res[name][balance.id] = balance_end
                elif name == 'balance_end':
                    res[name][balance.id] = balance_end 
                elif name == 'balance':
                    res[name][balance.id] = balance_dt-balance_ct
                elif name == 'debit':
                    res[name][balance.id] = debit
                elif name == 'credit':
                    res[name][balance.id] = credit

        return res

    def button_done(self, ids):
        return self.close(ids)

    def button_draft(self, ids):
        return self.cancel(ids)

    def button_restore(self, ids):
        return self.restore(ids)

    def close(self, ids):
        return self.write(ids, {'state': 'done', })

    def draft(self, ids):
        return self.write(ids, {'state': 'draft', })

    def restore(self, ids):
        return self.write(ids, {'state': 'draft', })
Example #6
0
class SaleMeeting(ModelView, ModelSQL):
    'Sale Opportunity Meeting'
    __name__ = 'sale.meeting'

    _order_name = 'hora'

    opportunity = fields.Many2One(
        'sale.opportunity',
        'Opportunity',
    )
    fecha = fields.Date('Fecha', required=True)
    hora = fields.Time('Hora', required=True)
    descripcion = fields.Text('Descripcion', required=True)
    asesor = fields.Many2One('company.employee',
                             'Asesor',
                             domain=[('company', '=', Eval('company'))],
                             required=True,
                             readonly=True)
    medio_contacto = fields.Selection(
        [
            ('Llamar', 'Llamar'),
            ('Correo', 'Enviar correo'),
            ('Whatsapp', 'Whatsapp'),
            ('SMS', 'SMS'),
            ('Visita', 'Visita'),
            ('Otro', 'Otro'),
        ],
        'Medio',
        sort=False,
        required=True,
    )
    hora_ = fields.Function(fields.Char('Hora'), 'get_hora')
    party = fields.Function(fields.Many2One('party.party', 'Cliente'),
                            'get_party')
    user = fields.Many2One('res.user', 'Usuario', readonly=True)

    def get_hora(self, name):
        hora = self.hora
        hour = self.hora.hour
        minute = self.hora.minute
        hora_ = 'Hora ' + str(hour) + ':' + str(minute)
        return hora_

    def get_party(self, name):
        if self.opportunity:
            return self.opportunity.party.id

    @classmethod
    def default_user(cls):
        pool = Pool()
        User = pool.get('res.user')
        cursor = Transaction().connection.cursor()
        user = User(Transaction().user).id
        return user

    @classmethod
    def default_date(cls):
        pool = Pool()
        Date = pool.get('ir.date')
        return Date.today()

    @classmethod
    def default_asesor(cls):
        User = Pool().get('res.user')
        employee_id = None
        if Transaction().context.get('employee'):
            employee_id = Transaction().context['employee']
        else:
            user = User(Transaction().user)
            if user.employee:
                employee_id = user.employee.id
        if employee_id:
            return employee_id
Example #7
0
class WebUser:
    __name__ = 'web.user'
    _rec_name = 'email'
    nickname = fields.Char('Nickname', help='The name shown to other users')
    user = fields.One2One(
        'web.user-res.user',
        'web_user',
        'res_user',
        'Tryton User',
        readonly=True,
        states={'required': Greater(Eval('active_id', -1), 0)},
        help='The Tryton user of the web user')
    party = fields.One2One(
        'web.user-party.party',
        'user',
        'party',
        'Party',
        states={'required': Greater(Eval('active_id', -1), 0)},
        help='The party of the web user')
    pocket_account = fields.Many2One('account.account', 'Account')
    clients = fields.One2Many('client', 'web_user', 'Clients')
    roles = fields.Many2Many('web.user-web.user.role', 'user', 'role', 'Roles')
    default_role = fields.Selection('get_roles', 'Default Role')
    show_creative_info = fields.Boolean('Show Creative Info',
                                        help='Check, if the infobox is shown')
    picture_data = fields.Binary('Picture Data', help='Picture Data')
    picture_data_mime_type = fields.Char('Picture Data Mime Type',
                                         help='The mime type of picture data.')
    opt_in_state = fields.Selection(
        _OPT_IN_STATES,
        'Opt-in State',
        help='The authentication state of the opt-in method:\n\n'
        'New: The web-user is newly created.\n'
        'Mail Sent: An opt-in link is sent to the email.\n'
        'Opted-In: The link is clicked.\n'
        'Opted-Out: The web-user opted-out.')
    opt_in_uuid = fields.Char(
        'Opt-in UUID',
        help='The universally unique identifier of the opt-in of a web user')
    opt_in_timestamp = fields.DateTime('Date of Opt-in')
    opt_out_timestamp = fields.DateTime('Date of Opt-out')

    @classmethod
    def __setup__(cls):
        super(WebUser, cls).__setup__()
        cls.__rpc__.update({'authenticate': RPC(check_access=False)})
        cls._sql_constraints += [
            ('opt_in_uuid_uniq', 'UNIQUE(opt_in_uuid)',
             'The opt-in UUID of the Webuser must be unique.'),
        ]

    @staticmethod
    def default_show_creative_info():
        return True

    @staticmethod
    def default_opt_in_state():
        return 'new'

    @staticmethod
    def default_opt_in_uuid():
        return str(uuid.uuid4())

    @staticmethod
    def get_roles():
        Role = Pool().get('web.user.role')
        roles = Role.search([])
        return [(x.code, x.name) for x in roles] + [(None, '')]

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        User = pool.get('res.user')
        Party = pool.get('party.party')

        vlist = [x.copy() for x in vlist]
        for values in vlist:
            email = values.get('email')
            user_email = email + ':::' + ''.join(
                random.sample(string.lowercase, 10))
            if not values.get('party'):
                values['party'] = Party.create([{'name': email}])[0].id
            if not values.get('user'):
                values['user'] = User.create([{
                    'name': user_email,
                    'login': user_email,
                    'email': email,
                    'active': False,
                }])[0].id

        return super(WebUser, cls).create(vlist)

    @classmethod
    def get_balance(cls, items, names):
        '''
        Function to compute hat balance for artist
        or pocket balance party items.
        '''
        res = {}
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Account = pool.get('account.account')
        User = pool.get('res.user')
        cursor = Transaction().cursor

        line = MoveLine.__table__()
        account = Account.__table__()

        for name in names:
            if name not in ('hat_balance', 'pocket_balance'):
                raise Exception('Bad argument')
            res[name] = dict((i.id, Decimal('0.0')) for i in items)

        user_id = Transaction().user
        if user_id == 0 and 'user' in Transaction().context:
            user_id = Transaction().context['user']
        user = User(user_id)
        if not user.company:
            return res
        company_id = user.company.id

        with Transaction().set_context(posted=False):
            line_query, _ = MoveLine.query_get(line)
        clause = ()
        if name == 'hat_balance':
            field = line.artist
            clause = line.artist.in_([i.id for i in items])
        if name == 'pocket_balance':
            field = line.party
            clause = line.party.in_([i.id for i in items])
        for name in names:
            query = line.join(
                account, condition=account.id == line.account).select(
                    field,
                    Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)),
                    where=account.active
                    & (account.kind == name[:-8])
                    & clause
                    & (line.reconciliation == None)
                    & (account.company == company_id)
                    & line_query,
                    group_by=field)
            cursor.execute(*query)
            for id_, sum in cursor.fetchall():
                # SQLite uses float for SUM
                if not isinstance(sum, Decimal):
                    sum = Decimal(str(sum))
                res[name][id_] = -sum
        return res

    @classmethod
    def search_balance(cls, name, clause):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Account = pool.get('account.account')
        Company = pool.get('company.company')
        User = pool.get('res.user')

        line = MoveLine.__table__()
        account = Account.__table__()

        if name not in ('hat_balance', 'pocket_balance'):
            raise Exception('Bad argument')

        company_id = None
        user_id = Transaction().user
        if user_id == 0 and 'user' in Transaction().context:
            user_id = Transaction().context['user']
        user = User(user_id)
        if Transaction().context.get('company'):
            child_companies = Company.search([
                ('parent', 'child_of', [user.main_company.id]),
            ])
            if Transaction().context['company'] in child_companies:
                company_id = Transaction().context['company']

        if not company_id:
            if user.company:
                company_id = user.company.id
            elif user.main_company:
                company_id = user.main_company.id

        if not company_id:
            return []

        line_query, _ = MoveLine.query_get(line)
        Operator = fields.SQL_OPERATORS[clause[1]]
        if name == 'hat_balance':
            field = line.artist
            where_clause = (line.artist != None)
            sign = -1
        if name == 'pocket_balance':
            field = line.party
            where_clause = (line.party != None)
            sign = 1
        query = line.join(account,
                          condition=account.id == line.account).select(
                              field,
                              where=account.active
                              & (account.kind == 'hat')
                              & where_clause
                              & (line.reconciliation == None)
                              & (account.company == company_id)
                              & line_query,
                              group_by=field,
                              having=Operator(
                                  Mul(
                                      sign,
                                      Sum(
                                          Coalesce(line.debit, 0) -
                                          Coalesce(line.credit, 0))),
                                  Decimal(clause[2] or 0)))
        return [('id', 'in', query)]
class Line(sequence_ordered(), ModelSQL, ModelView):
    "Subscription Line"
    __name__ = 'sale.subscription.line'

    subscription = fields.Many2One('sale.subscription',
                                   "Subscription",
                                   required=True,
                                   select=True,
                                   ondelete='CASCADE',
                                   states={
                                       'readonly':
                                       ((Eval('subscription_state') != 'draft')
                                        & Bool(Eval('subscription'))),
                                   },
                                   depends=['subscription_state'],
                                   help="Add the line below the subscription.")
    subscription_state = fields.Function(
        fields.Selection(STATES, "Subscription State"),
        'on_change_with_subscription_state')
    subscription_start_date = fields.Function(
        fields.Date("Subscription Start Date"),
        'on_change_with_subscription_start_date')
    subscription_end_date = fields.Function(
        fields.Date("Subscription End Date"),
        'on_change_with_subscription_end_date')

    service = fields.Many2One('sale.subscription.service',
                              "Service",
                              required=True,
                              states={
                                  'readonly':
                                  Eval('subscription_state') != 'draft',
                              },
                              depends=['subscription_state'])
    description = fields.Text("Description",
                              states={
                                  'readonly':
                                  Eval('subscription_state') != 'draft',
                              },
                              depends=['subscription_state'])

    quantity = fields.Float("Quantity",
                            digits=(16, Eval('unit_digits', 2)),
                            states={
                                'readonly':
                                Eval('subscription_state') != 'draft',
                                'required':
                                Bool(Eval('consumption_recurrence')),
                            },
                            depends=[
                                'unit_digits', 'subscription_state',
                                'consumption_recurrence'
                            ])
    unit = fields.Many2One(
        'product.uom',
        "Unit",
        required=True,
        states={
            'readonly': Eval('subscription_state') != 'draft',
        },
        domain=[
            If(Bool(Eval('service_unit_category')),
               ('category', '=', Eval('service_unit_category')),
               ('category', '!=', -1)),
        ],
        depends=['subscription_state', 'service_unit_category'])
    unit_digits = fields.Function(fields.Integer("Unit Digits"),
                                  'on_change_with_unit_digits')
    service_unit_category = fields.Function(
        fields.Many2One('product.uom.category', "Service Unit Category"),
        'on_change_with_service_unit_category')

    unit_price = fields.Numeric("Unit Price",
                                digits=price_digits,
                                states={
                                    'readonly':
                                    Eval('subscription_state') != 'draft',
                                },
                                depends=['subscription_state'])

    consumption_recurrence = fields.Many2One(
        'sale.subscription.recurrence.rule.set',
        "Consumption Recurrence",
        states={
            'readonly': Eval('subscription_state') != 'draft',
        },
        depends=['subscription_state'])
    consumption_delay = fields.TimeDelta(
        "Consumption Delay",
        states={
            'readonly': Eval('subscription_state') != 'draft',
            'invisible': ~Eval('consumption_recurrence'),
        },
        depends=['subscription_state', 'consumption_recurrence'])
    next_consumption_date = fields.Date("Next Consumption Date", readonly=True)
    next_consumption_date_delayed = fields.Function(
        fields.Date("Next Consumption Delayed"),
        'get_next_consumption_date_delayed')
    consumed = fields.Boolean("Consumed")
    start_date = fields.Date(
        "Start Date",
        required=True,
        domain=[
            ('start_date', '>=', Eval('subscription_start_date')),
        ],
        states={
            'readonly': ((Eval('subscription_state') != 'draft')
                         | Eval('consumed')),
        },
        depends=['subscription_start_date', 'subscription_state', 'consumed'])
    end_date = fields.Date(
        "End Date",
        domain=[
            'OR',
            [
                ('end_date', '>=', Eval('start_date')),
                If(Bool(Eval('subscription_end_date')),
                   ('end_date', '<=', Eval('subscription_end_date')), ()),
                If(Bool(Eval('next_consumption_date')),
                   ('end_date', '>=', Eval('next_consumption_date')), ()),
            ],
            ('end_date', '=', None),
        ],
        states={
            'readonly':
            ((Eval('subscription_state') != 'draft')
             | (~Eval('next_consumption_date') & Eval('consumed'))),
        },
        depends=[
            'subscription_end_date', 'start_date', 'next_consumption_date',
            'subscription_state', 'consumed'
        ])

    @classmethod
    def __register__(cls, module):
        pool = Pool()
        Subscription = pool.get('sale.subscription')
        TableHandler = backend.get('TableHandler')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        table = cls.__table__()
        subscription = Subscription.__table__()

        # Migration from 4.8: start_date required
        if TableHandler.table_exist(cls._table):
            table_h = cls.__table_handler__(module)
            if table_h.column_exist('start_date'):
                cursor.execute(
                    *table.update([table.start_date],
                                  subscription.select(subscription.start_date,
                                                      where=subscription.id ==
                                                      table.subscription),
                                  where=table.start_date == Null))

        super(Line, cls).__register__(module)
        table_h = cls.__table_handler__(module)

        # Migration from 4.8: drop required on description
        table_h.not_null_action('description', action='remove')

    @fields.depends('subscription', '_parent_subscription.state')
    def on_change_with_subscription_state(self, name=None):
        if self.subscription:
            return self.subscription.state

    @fields.depends('subscription', '_parent_subscription.start_date')
    def on_change_with_subscription_start_date(self, name=None):
        if self.subscription:
            return self.subscription.start_date

    @fields.depends('subscription', '_parent_subscription.end_date')
    def on_change_with_subscription_end_date(self, name=None):
        if self.subscription:
            return self.subscription.end_date

    @fields.depends('subscription', 'start_date', 'end_date',
                    '_parent_subscription.start_date',
                    '_parent_subscription.end_date')
    def on_change_subscription(self):
        if self.subscription:
            if not self.start_date:
                self.start_date = self.subscription.start_date
            if not self.end_date:
                self.end_date = self.subscription.end_date

    @classmethod
    def default_quantity(cls):
        return 1

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

    @fields.depends('service')
    def on_change_with_service_unit_category(self, name=None):
        if self.service:
            return self.service.product.default_uom_category.id

    @fields.depends('service',
                    'quantity',
                    'unit',
                    'subscription',
                    '_parent_subscription.party',
                    methods=['_get_context_sale_price'])
    def on_change_service(self):
        pool = Pool()
        Product = pool.get('product.product')

        if not self.service:
            self.consumption_recurrence = None
            self.consumption_delay = None
            return

        party = None
        party_context = {}
        if self.subscription and self.subscription.party:
            party = self.subscription.party
            if party.lang:
                party_context['language'] = party.lang.code

        product = self.service.product
        category = product.sale_uom.category
        if not self.unit or self.unit.category != category:
            self.unit = product.sale_uom
            self.unit_digits = product.sale_uom.digits

        with Transaction().set_context(self._get_context_sale_price()):
            self.unit_price = Product.get_sale_price([product], self.quantity
                                                     or 0)[product.id]
            if self.unit_price:
                self.unit_price = self.unit_price.quantize(
                    Decimal(1) / 10**self.__class__.unit_price.digits[1])

        self.consumption_recurrence = self.service.consumption_recurrence
        self.consumption_delay = self.service.consumption_delay

    @fields.depends('subscription', '_parent_subscription.currency',
                    '_parent_subscription.party', 'start_date', 'unit',
                    'service')
    def _get_context_sale_price(self):
        context = {}
        if self.subscription:
            if self.subscription.currency:
                context['currency'] = self.subscription.currency.id
            if self.subscription.party:
                context['customer'] = self.subscription.party.id
            if self.start_date:
                context['sale_date'] = self.start_date
        if self.unit:
            context['uom'] = self.unit.id
        elif self.service:
            context['uom'] = self.service.sale_uom.id
        # TODO tax
        return context

    def get_next_consumption_date_delayed(self, name=None):
        if self.next_consumption_date and self.consumption_delay:
            return self.next_consumption_date + self.consumption_delay
        return self.next_consumption_date

    @classmethod
    def default_consumed(cls):
        return False

    def get_rec_name(self, name):
        return '%s @ %s' % (self.service.rec_name, self.subscription.rec_name)

    @classmethod
    def search_rec_name(cls, name, clause):
        return [
            'OR',
            ('subscription.rec_name', ) + tuple(clause[1:]),
            ('service.rec_name', ) + tuple(clause[1:]),
        ]

    @classmethod
    def domain_next_consumption_date_delayed(cls, domain, tables):
        field = cls.next_consumption_date_delayed._field
        table, _ = tables[None]
        name, operator, value = domain
        Operator = fields.SQL_OPERATORS[operator]
        column = (table.next_consumption_date +
                  Coalesce(table.consumption_delay, datetime.timedelta()))
        expression = Operator(column, field._domain_value(operator, value))
        if isinstance(expression, operators.In) and not expression.right:
            expression = Literal(False)
        elif isinstance(expression, operators.NotIn) and not expression.right:
            expression = Literal(True)
        expression = field._domain_add_null(column, operator, value,
                                            expression)
        return expression

    @classmethod
    def generate_consumption(cls, date=None):
        pool = Pool()
        Date = pool.get('ir.date')
        Consumption = pool.get('sale.subscription.line.consumption')
        Subscription = pool.get('sale.subscription')

        if date is None:
            date = Date.today()

        remainings = all_lines = cls.search([
            ('consumption_recurrence', '!=', None),
            ('next_consumption_date_delayed', '<=', date),
            ('subscription.state', '=', 'running'),
        ])

        consumptions = []
        subscription_ids = set()
        while remainings:
            lines, remainings = remainings, []
            for line in lines:
                consumptions.append(
                    line.get_consumption(line.next_consumption_date))
                line.next_consumption_date = (
                    line.compute_next_consumption_date())
                line.consumed = True
                if line.next_consumption_date is None:
                    subscription_ids.add(line.subscription.id)
                elif line.get_next_consumption_date_delayed() <= date:
                    remainings.append(line)

        Consumption.save(consumptions)
        cls.save(all_lines)
        Subscription.process(Subscription.browse(list(subscription_ids)))

    def get_consumption(self, date):
        pool = Pool()
        Consumption = pool.get('sale.subscription.line.consumption')
        return Consumption(line=self, quantity=self.quantity, date=date)

    def compute_next_consumption_date(self):
        if not self.consumption_recurrence:
            return None
        date = self.next_consumption_date or self.start_date
        rruleset = self.consumption_recurrence.rruleset(self.start_date)
        dt = datetime.datetime.combine(date, datetime.time())
        inc = (self.start_date == date) and not self.next_consumption_date
        next_date = rruleset.after(dt, inc=inc).date()
        for end_date in [self.end_date, self.subscription.end_date]:
            if end_date:
                if next_date > end_date:
                    return None
        return next_date

    @classmethod
    def copy(cls, lines, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('next_consumption_date', None)
        default.setdefault('consumed', None)
        return super(Line, cls).copy(lines, default=default)
Example #9
0
class Inventory(Workflow, ModelSQL, ModelView):
    'Stock Inventory'
    __name__ = 'stock.inventory'
    _rec_name = 'number'
    number = fields.Char('Number', readonly=True)
    location = fields.Many2One('stock.location',
                               'Location',
                               required=True,
                               domain=[('type', '=', 'storage')],
                               states={
                                   'readonly': (Eval('state') != 'draft')
                                   | Eval('lines', [0]),
                               },
                               depends=['state'])
    date = fields.Date('Date',
                       required=True,
                       states={
                           'readonly':
                           (Eval('state') != 'draft') | Eval('lines', [0]),
                       },
                       depends=['state'])
    lost_found = fields.Many2One('stock.location',
                                 'Lost and Found',
                                 required=True,
                                 domain=[('type', '=', 'lost_found')],
                                 states=STATES,
                                 depends=DEPENDS)
    lines = fields.One2Many('stock.inventory.line',
                            'inventory',
                            'Lines',
                            states={
                                'readonly':
                                (STATES['readonly'] | ~Eval('location')
                                 | ~Eval('date')),
                            },
                            depends=['location', 'date'] + DEPENDS)
    empty_quantity = fields.Selection(
        [
            (None, ""),
            ('keep', "Keep"),
            ('empty', "Empty"),
        ],
        "Empty Quantity",
        states=STATES,
        depends=DEPENDS,
        help="How lines without quantity are handled.")
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              states={
                                  'readonly': (Eval('state') != 'draft')
                                  | Eval('lines', [0]),
                              },
                              depends=['state'])
    state = fields.Selection(INVENTORY_STATES,
                             'State',
                             readonly=True,
                             select=True)

    @classmethod
    def __setup__(cls):
        super(Inventory, cls).__setup__()
        cls._order.insert(0, ('date', 'DESC'))
        cls._error_messages.update({
            'delete_cancel': ('Inventory "%s" must be canceled before '
                              'deletion.'),
            'unique_line': ('Line "%s" is not unique '
                            'on Inventory "%s".'),
        })
        cls._transitions |= set((
            ('draft', 'done'),
            ('draft', 'cancel'),
        ))
        cls._buttons.update({
            'confirm': {
                'invisible': Eval('state').in_(['done', 'cancel']),
                'depends': ['state'],
            },
            'cancel': {
                'invisible': Eval('state').in_(['cancel', 'done']),
                'depends': ['state'],
            },
            'complete_lines': {
                'readonly': Eval('state') != 'draft',
                'depends': ['state'],
            },
            'count': {
                'readonly': Eval('state') != 'draft',
                'depends': ['state'],
            },
        })

    @classmethod
    def __register__(cls, module_name):
        super(Inventory, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)

        # Add index on create_date
        table.index_action('create_date', action='add')

    @staticmethod
    def default_state():
        return 'draft'

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

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

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

    @classmethod
    def delete(cls, inventories):
        # Cancel before delete
        cls.cancel(inventories)
        for inventory in inventories:
            if inventory.state != 'cancel':
                cls.raise_user_error('delete_cancel', inventory.rec_name)
        super(Inventory, cls).delete(inventories)

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def confirm(cls, inventories):
        Move = Pool().get('stock.move')
        moves = []
        for inventory in inventories:
            keys = set()
            for line in inventory.lines:
                key = line.unique_key
                if key in keys:
                    cls.raise_user_error('unique_line',
                                         (line.rec_name, inventory.rec_name))
                keys.add(key)
                move = line.get_move()
                if move:
                    moves.append(move)
        if moves:
            Move.save(moves)
            Move.do(moves)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, inventories):
        Line = Pool().get("stock.inventory.line")
        Line.cancel_move([l for i in inventories for l in i.lines])

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Configuration = pool.get('stock.configuration')
        config = Configuration(1)
        vlist = [x.copy() for x in vlist]
        for values in vlist:
            if values.get('number') is None:
                values['number'] = Sequence.get_id(
                    config.inventory_sequence.id)
        inventories = super(Inventory, cls).create(vlist)
        cls.complete_lines(inventories, fill=False)
        return inventories

    @classmethod
    def write(cls, inventories, values):
        super(Inventory, cls).write(inventories, values)
        cls.complete_lines(inventories, fill=False)

    @classmethod
    def copy(cls, inventories, default=None):
        pool = Pool()
        Date = pool.get('ir.date')

        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('date', Date.today())
        default.setdefault('lines.moves', None)
        default.setdefault('number', None)

        new_inventories = super().copy(inventories, default=default)
        cls.complete_lines(new_inventories, fill=False)
        return new_inventories

    @staticmethod
    def grouping():
        return ('product', )

    @classmethod
    @ModelView.button
    def complete_lines(cls, inventories, fill=True):
        '''
        Complete or update the inventories
        '''
        pool = Pool()
        Line = pool.get('stock.inventory.line')
        Product = pool.get('product.product')

        grouping = cls.grouping()
        to_create, to_write = [], []
        for inventory in inventories:
            # Once done computation is wrong because include created moves
            if inventory.state == 'done':
                continue
            # Compute product quantities
            if fill:
                product_ids = None
            else:
                product_ids = [l.product.id for l in inventory.lines]
            with Transaction().set_context(stock_date_end=inventory.date):
                pbl = Product.products_by_location(
                    [inventory.location.id],
                    grouping=grouping,
                    grouping_filter=(product_ids, ))

            # Index some data
            product2type = {}
            product2consumable = {}
            for product in Product.browse([line[1] for line in pbl]):
                product2type[product.id] = product.type
                product2consumable[product.id] = product.consumable

            # Update existing lines
            for line in inventory.lines:
                if not (line.product.type == 'goods'
                        and not line.product.consumable):
                    Line.delete([line])
                    continue

                key = (inventory.location.id, ) + line.unique_key
                if key in pbl:
                    quantity = pbl.pop(key)
                else:
                    quantity = 0.0
                values = line.update_values4complete(quantity)
                if values:
                    to_write.extend(([line], values))

            if not fill:
                continue
            # Create lines if needed
            for key, quantity in pbl.items():
                product_id = key[grouping.index('product') + 1]
                if (product2type[product_id] != 'goods'
                        or product2consumable[product_id]):
                    continue
                if not quantity:
                    continue

                values = Line.create_values4complete(inventory, quantity)
                for i, fname in enumerate(grouping, 1):
                    values[fname] = key[i]
                to_create.append(values)
        if to_create:
            Line.create(to_create)
        if to_write:
            Line.write(*to_write)

    @classmethod
    @ModelView.button_action('stock.wizard_inventory_count')
    def count(cls, inventories):
        cls.complete_lines(inventories)
Example #10
0
class Inventory(Workflow, ModelSQL, ModelView):
    'Stock Inventory'
    __name__ = 'stock.inventory'
    location = fields.Many2One('stock.location',
                               'Location',
                               required=True,
                               domain=[('type', '=', 'storage')],
                               states={
                                   'readonly':
                                   Or(Not(Equal(Eval('state'), 'draft')),
                                      Bool(Eval('lines', [0]))),
                               },
                               depends=['state'])
    date = fields.Date('Date',
                       required=True,
                       states={
                           'readonly':
                           Or(Not(Equal(Eval('state'), 'draft')),
                              Bool(Eval('lines', [0]))),
                       },
                       depends=['state'])
    lost_found = fields.Many2One('stock.location',
                                 'Lost and Found',
                                 required=True,
                                 domain=[('type', '=', 'lost_found')],
                                 states=STATES,
                                 depends=DEPENDS)
    lines = fields.One2Many('stock.inventory.line',
                            'inventory',
                            'Lines',
                            states=STATES,
                            depends=DEPENDS)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              states={
                                  'readonly':
                                  Or(Not(Equal(Eval('state'), 'draft')),
                                     Bool(Eval('lines', [0]))),
                              },
                              depends=['state'])
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'Done'),
        ('cancel', 'Canceled'),
    ],
                             'State',
                             readonly=True,
                             select=True)
    batch_number = fields.Char('Batch Number')

    @classmethod
    def __setup__(cls):
        super(Inventory, cls).__setup__()
        cls._order.insert(0, ('date', 'DESC'))
        cls._error_messages.update({
            'delete_cancel': ('Inventory "%s" must be cancelled before '
                              'deletion.'),
            'unique_line': ('Line "%s" is not unique '
                            'on Inventory "%s".'),
        })
        cls._transitions |= set((
            ('draft', 'done'),
            ('draft', 'cancel'),
        ))
        cls._buttons.update({
            'confirm': {
                'invisible': Eval('state').in_(['done', 'cancel']),
            },
            'cancel': {
                'invisible': Eval('state').in_(['cancel', 'done']),
            },
            'complete_lines': {
                'readonly': Eval('state') != 'draft',
            },
        })

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

        # Add index on create_date
        table = TableHandler(cursor, cls, module_name)
        table.index_action('create_date', action='add')

    @staticmethod
    def default_state():
        return 'draft'

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

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

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

    @classmethod
    def delete(cls, inventories):
        # Cancel before delete
        cls.cancel(inventories)
        for inventory in inventories:
            if inventory.state != 'cancel':
                cls.raise_user_error('delete_cancel', inventory.rec_name)
        super(Inventory, cls).delete(inventories)

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def confirm(cls, inventories):
        Move = Pool().get('stock.move')
        moves = []
        for inventory in inventories:
            keys = set()
            for line in inventory.lines:
                key = line.unique_key
                if key in keys:
                    cls.raise_user_error('unique_line',
                                         (line.rec_name, inventory.rec_name))
                keys.add(key)
                move = line.get_move()
                if move:
                    moves.append(move)
        if moves:
            Move.save(moves)
            Move.do(moves)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, inventories):
        Line = Pool().get("stock.inventory.line")
        Line.cancel_move([l for i in inventories for l in i.lines])

    @classmethod
    def copy(cls, inventories, default=None):
        pool = Pool()
        Date = pool.get('ir.date')
        Line = pool.get('stock.inventory.line')

        if default is None:
            default = {}
        default = default.copy()
        default['date'] = Date.today()
        default['lines'] = None

        new_inventories = []
        for inventory in inventories:
            new_inventory, = super(Inventory, cls).copy([inventory],
                                                        default=default)
            Line.copy(inventory.lines,
                      default={
                          'inventory': new_inventory.id,
                          'moves': None,
                      })
            cls.complete_lines([new_inventory])
            new_inventories.append(new_inventory)
        return new_inventories

    @staticmethod
    def complete_lines(inventories):
        '''
        Complete or update the inventories
        '''
        pool = Pool()
        Line = pool.get('stock.inventory.line')
        Product = pool.get('product.product')

        to_create = []
        for inventory in inventories:
            # Compute product quantities
            with Transaction().set_context(stock_date_end=inventory.date):
                pbl = Product.products_by_location([inventory.location.id])

            # Index some data
            product2uom = {}
            product2type = {}
            product2consumable = {}
            for product in Product.browse([line[1] for line in pbl]):
                product2uom[product.id] = product.default_uom.id
                product2type[product.id] = product.type
                product2consumable[product.id] = product.consumable

            product_qty = {}
            for (location, product), quantity in pbl.iteritems():
                product_qty[product] = (quantity, product2uom[product])

            # Update existing lines
            for line in inventory.lines:
                if not (line.product.active and line.product.type == 'goods'
                        and not line.product.consumable):
                    Line.delete([line])
                    continue
                if line.product.id in product_qty:
                    quantity, uom_id = product_qty.pop(line.product.id)
                elif line.product.id in product2uom:
                    quantity, uom_id = 0.0, product2uom[line.product.id]
                else:
                    quantity, uom_id = 0.0, line.product.default_uom.id
                values = line.update_values4complete(quantity, uom_id)
                if values:
                    Line.write([line], values)

            # Create lines if needed
            for product_id in product_qty:
                if (product2type[product_id] != 'goods'
                        or product2consumable[product_id]):
                    continue
                quantity, uom_id = product_qty[product_id]
                if not quantity:
                    continue
                values = Line.create_values4complete(product_id, inventory,
                                                     quantity, uom_id)
                to_create.append(values)
        if to_create:
            Line.create(to_create)
Example #11
0
class Subscription(Workflow, ModelSQL, ModelView):
    "Subscription"
    __name__ = 'sale.subscription'
    _rec_name = 'number'

    company = fields.Many2One(
        'company.company',
        "Company",
        required=True,
        select=True,
        states={
            'readonly': Eval('state') != 'draft',
        },
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        depends=['state'],
        help="Make the subscription belong to the company.")

    number = fields.Char("Number",
                         readonly=True,
                         select=True,
                         help="The main identification of the subscription.")
    # TODO revision
    reference = fields.Char("Reference",
                            select=True,
                            help="The identification of an external origin.")
    description = fields.Char("Description",
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              depends=['state'])

    party = fields.Many2One('party.party',
                            "Party",
                            required=True,
                            states={
                                'readonly':
                                ((Eval('state') != 'draft')
                                 | (Eval('lines', [0]) & Eval('party'))),
                            },
                            depends=['state'],
                            help="The party who subscribes.")
    invoice_address = fields.Many2One('party.address',
                                      "Invoice Address",
                                      domain=[
                                          ('party', '=', Eval('party')),
                                      ],
                                      states={
                                          'readonly':
                                          Eval('state') != 'draft',
                                          'required':
                                          ~Eval('state').in_(['draft']),
                                      },
                                      depends=['party', 'state'])
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   "Payment Term",
                                   states={
                                       'readonly': Eval('state') != 'draft',
                                   },
                                   depends=['state'])

    currency = fields.Many2One(
        'currency.currency',
        "Currency",
        required=True,
        states={
            'readonly': ((Eval('state') != 'draft')
                         | (Eval('lines', [0]) & Eval('currency', 0))),
        },
        depends=['state'])

    start_date = fields.Date("Start Date",
                             required=True,
                             states={
                                 'readonly': ((Eval('state') != 'draft')
                                              | Eval('next_invoice_date')),
                             },
                             depends=['state', 'next_invoice_date'])
    end_date = fields.Date("End Date",
                           domain=[
                               'OR',
                               ('end_date', '>=',
                                If(Bool(Eval('start_date')),
                                   Eval('start_date', datetime.date.min),
                                   datetime.date.min)),
                               ('end_date', '=', None),
                           ],
                           states={
                               'readonly': Eval('state') != 'draft',
                           },
                           depends=['start_date', 'state'])

    invoice_recurrence = fields.Many2One(
        'sale.subscription.recurrence.rule.set',
        "Invoice Recurrence",
        required=True,
        states={
            'readonly': Eval('state') != 'draft',
        },
        depends=['state'])
    invoice_start_date = fields.Date("Invoice Start Date",
                                     states={
                                         'readonly':
                                         ((Eval('state') != 'draft')
                                          | Eval('next_invoice_date')),
                                     },
                                     depends=['state', 'next_invoice_date'])
    next_invoice_date = fields.Date("Next Invoice Date", readonly=True)

    lines = fields.One2Many('sale.subscription.line',
                            'subscription',
                            "Lines",
                            states={
                                'readonly': ((Eval('state') != 'draft')
                                             | ~Eval('start_date')),
                            },
                            depends=['state'])

    state = fields.Selection(STATES,
                             "State",
                             readonly=True,
                             required=True,
                             help="The current state of the subscription.")

    @classmethod
    def __setup__(cls):
        super(Subscription, cls).__setup__()
        cls._order = [
            ('start_date', 'DESC'),
            ('id', 'DESC'),
        ]
        cls._transitions |= set((
            ('draft', 'canceled'),
            ('draft', 'quotation'),
            ('quotation', 'canceled'),
            ('quotation', 'draft'),
            ('quotation', 'running'),
            ('running', 'draft'),
            ('running', 'closed'),
            ('canceled', 'draft'),
        ))
        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['draft', 'quotation']),
                'icon': 'tryton-cancel',
                'depends': ['state'],
            },
            'draft': {
                'invisible':
                Eval('state').in_(['draft', 'closed']),
                'icon':
                If(Eval('state') == 'canceled', 'tryton-undo', 'tryton-back'),
                'depends': ['state'],
            },
            'quote': {
                'invisible': Eval('state') != 'draft',
                'readonly': ~Eval('lines', []),
                'icon': 'tryton-forward',
                'depends': ['state'],
            },
            'run': {
                'invisible': Eval('state') != 'quotation',
                'icon': 'tryton-forward',
                'depends': ['state'],
            },
        })

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

    @classmethod
    def default_currency(cls):
        pool = Pool()
        Company = pool.get('company.company')
        company = cls.default_company()
        if company:
            return Company(company).currency.id

    @classmethod
    def default_state(cls):
        return 'draft'

    @fields.depends('party')
    def on_change_party(self):
        self.invoice_address = None
        if self.party:
            self.invoice_address = self.party.address_get(type='invoice')
            self.payment_term = self.party.customer_payment_term

    @classmethod
    def set_number(cls, subscriptions):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('sale.configuration')

        config = Config(1)
        for subscription in subscriptions:
            if subscription.number:
                continue
            subscription.number = Sequence.get_id(
                config.subscription_sequence.id)
        cls.save(subscriptions)

    def compute_next_invoice_date(self):
        start_date = self.invoice_start_date or self.start_date
        date = self.next_invoice_date or self.start_date
        rruleset = self.invoice_recurrence.rruleset(start_date)
        dt = datetime.datetime.combine(date, datetime.time())
        inc = (start_date == date) and not self.next_invoice_date
        next_date = rruleset.after(dt, inc=inc)
        return next_date.date()

    @classmethod
    def copy(cls, subscriptions, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('number', None)
        default.setdefault('next_invoice_date', None)
        return super(Subscription, cls).copy(subscriptions, default=default)

    @classmethod
    @ModelView.button
    @Workflow.transition('canceled')
    def cancel(cls, subscriptions):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, subscriptions):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('quotation')
    def quote(cls, subscriptions):
        cls.set_number(subscriptions)

    @classmethod
    @ModelView.button
    @Workflow.transition('running')
    def run(cls, subscriptions):
        pool = Pool()
        Line = pool.get('sale.subscription.line')
        lines = []
        for subscription in subscriptions:
            if not subscription.next_invoice_date:
                subscription.next_invoice_date = (
                    subscription.compute_next_invoice_date())
            for line in subscription.lines:
                if (line.next_consumption_date is None and not line.consumed):
                    line.next_consumption_date = (
                        line.compute_next_consumption_date())
            lines.extend(subscription.lines)
        Line.save(lines)
        cls.save(subscriptions)

    @classmethod
    def process(cls, subscriptions):
        to_close = []
        for subscription in subscriptions:
            if all(l.next_consumption_date is None
                   for l in subscription.lines):
                to_close.append(subscription)
        cls.close(to_close)

    @classmethod
    @Workflow.transition('closed')
    def close(cls, subscriptions):
        pass

    @classmethod
    def generate_invoice(cls, date=None):
        pool = Pool()
        Date = pool.get('ir.date')
        Consumption = pool.get('sale.subscription.line.consumption')
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')

        if date is None:
            date = Date.today()

        consumptions = Consumption.search([
            ('invoice_line', '=', None),
            ('line.subscription.next_invoice_date', '<=', date),
            ('line.subscription.state', 'in', ['running', 'closed']),
        ],
                                          order=[
                                              ('line.subscription.id', 'DESC'),
                                          ])

        def keyfunc(consumption):
            return consumption.line.subscription

        invoices = {}
        lines = {}
        for subscription, consumptions in groupby(consumptions, key=keyfunc):
            invoices[subscription] = invoice = subscription._get_invoice()
            lines[subscription] = Consumption.get_invoice_lines(
                consumptions, invoice)

        all_invoices = list(invoices.values())
        Invoice.save(all_invoices)

        all_invoice_lines = []
        for subscription, invoice in invoices.items():
            invoice_lines, _ = lines[subscription]
            for line in invoice_lines:
                line.invoice = invoice
            all_invoice_lines.extend(invoice_lines)
        InvoiceLine.save(all_invoice_lines)

        all_consumptions = []
        for values in lines.values():
            for invoice_line, consumptions in zip(*values):
                for consumption in consumptions:
                    assert not consumption.invoice_line
                    consumption.invoice_line = invoice_line
                    all_consumptions.append(consumption)
        Consumption.save(all_consumptions)

        Invoice.update_taxes(all_invoices)

        subscriptions = cls.search([
            ('next_invoice_date', '<=', date),
        ])
        for subscription in subscriptions:
            if subscription.state == 'running':
                while subscription.next_invoice_date <= date:
                    subscription.next_invoice_date = (
                        subscription.compute_next_invoice_date())
            else:
                subscription.next_invoice_date = None
        cls.save(subscriptions)

    def _get_invoice(self):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        invoice = Invoice(
            company=self.company,
            type='out',
            party=self.party,
            invoice_address=self.invoice_address,
            currency=self.currency,
            account=self.party.account_receivable,
        )
        invoice.on_change_type()
        invoice.payment_term = self.payment_term
        return invoice
Example #12
0
class SaleLine(metaclass=PoolMeta):
    __name__ = 'sale.line'

    purchase_request = fields.Many2One('purchase.request',
                                       'Purchase Request',
                                       ondelete='SET NULL',
                                       readonly=True)
    purchase_request_state = fields.Function(
        fields.Selection([
            ('', ''),
            ('requested', 'Requested'),
            ('purchased', 'Purchased'),
            ('cancel', 'Cancel'),
        ],
                         'Purchase Request State',
                         states={
                             'invisible': ~Eval('purchase_request_state'),
                         }), 'get_purchase_request_state')

    def get_purchase_request_state(self, name):
        if self.purchase_request is not None:
            purchase_line = self.purchase_request.purchase_line
            if purchase_line is not None:
                purchase = purchase_line.purchase
                if purchase.state == 'cancel':
                    return 'cancel'
                elif purchase.state in ('processing', 'done'):
                    return 'purchased'
            return 'requested'
        return ''

    @classmethod
    def copy(cls, lines, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('purchase_request', None)
        return super(SaleLine, cls).copy(lines, default=default)

    @property
    def supply_on_sale(self):
        "Returns True if the sale line has to be supply by purchase request"
        if (self.type != 'line' or not self.product or self.quantity <= 0
                or not self.product.purchasable
                or any(m.state not in ['staging', 'cancel']
                       for m in self.moves)):
            return False
        return self.product.supply_on_sale

    def get_move(self, shipment_type):
        move = super(SaleLine, self).get_move(shipment_type)
        if (move and shipment_type == 'out'
                and (self.supply_on_sale or self.purchase_request)):
            if self.purchase_request_state in ('', 'requested'):
                move.state = 'staging'
        return move

    def _get_purchase_request_product_supplier_pattern(self):
        return {
            'company': self.sale.company.id,
        }

    def get_purchase_request(self):
        'Return purchase request for the sale line'
        pool = Pool()
        Uom = pool.get('product.uom')
        Request = pool.get('purchase.request')

        if not self.supply_on_sale or self.purchase_request:
            return

        # Ensure to create the request for the maximum paid
        if self.sale.shipment_method == 'invoice':
            invoice_skips = (set(self.sale.invoices_ignored)
                             | set(self.sale.invoices_recreated))
            invoice_lines = [
                l for l in self.invoice_lines if l.invoice not in invoice_skips
            ]
            if (not invoice_lines or any(
                (not l.invoice) or l.invoice.state != 'paid'
                    for l in invoice_lines)):
                return

        product = self.product
        supplier, purchase_date = Request.find_best_supplier(
            product, self.shipping_date,
            **self._get_purchase_request_product_supplier_pattern())
        uom = product.purchase_uom or product.default_uom
        quantity = self._get_move_quantity('out')
        quantity = Uom.compute_qty(self.unit, quantity, uom)
        return Request(
            product=product,
            party=supplier,
            quantity=quantity,
            uom=uom,
            computed_quantity=quantity,
            computed_uom=uom,
            purchase_date=purchase_date,
            supply_date=self.shipping_date,
            company=self.sale.company,
            warehouse=self.warehouse,
            origin=self.sale,
        )

    def assign_supplied(self, location_quantities):
        '''
        Assign supplied move

        location_quantities will be updated according to assigned
        quantities.
        '''
        pool = Pool()
        Uom = pool.get('product.uom')
        Move = pool.get('stock.move')

        if self.purchase_request_state != 'purchased':
            return
        moves = set()
        for move in self.moves:
            for inv_move in move.shipment.inventory_moves:
                if inv_move.product.id == self.product.id:
                    moves.add(inv_move)
        for move in moves:
            if move.state != 'draft':
                continue
            location_qties_converted = {}
            for location_id, quantity in location_quantities.items():
                location_qties_converted[location_id] = (Uom.compute_qty(
                    move.product.default_uom, quantity, move.uom, round=False))
            to_pick = move.pick_product(location_qties_converted)

            picked_qties = sum(qty for _, qty in to_pick)
            if picked_qties < move.quantity:
                first = False
                Move.write([move], {
                    'quantity': move.quantity - picked_qties,
                })
            else:
                first = True
            for from_location, qty in to_pick:
                values = {
                    'from_location': from_location.id,
                    'quantity': qty,
                }
                if first:
                    Move.write([move], values)
                    Move.assign([move])
                else:
                    Move.assign(Move.copy([move], default=values))

                qty_default_uom = Uom.compute_qty(move.uom,
                                                  qty,
                                                  move.product.default_uom,
                                                  round=False)

                location_quantities[from_location] -= qty_default_uom
Example #13
0
class MenuItem(ModelSQL, ModelView, CMSMenuItemMixin):
    "Nereid CMS Menuitem"
    __name__ = 'nereid.cms.menuitem'
    _rec_name = 'title'

    type_ = fields.Selection([
        ('view', 'View'),
        ('static', 'Static'),
        ('record', 'Record'),
    ],
                             'Type',
                             required=True,
                             select=True)
    active = fields.Boolean('Active', select=True)
    title = fields.Char('Title',
                        required=True,
                        select=True,
                        translate=True,
                        depends=['type_'],
                        states={
                            'required': Eval('type_') == 'static',
                        })
    link = fields.Char('Link',
                       states={
                           'required': Eval('type_') == 'static',
                           'invisible': Eval('type_') != 'static',
                       },
                       depends=['type_'])
    target = fields.Selection([
        ('_self', 'Self'),
        ('_blank', 'Blank'),
    ],
                              'Target',
                              required=True)

    parent = fields.Many2One('nereid.cms.menuitem',
                             'Parent Menuitem',
                             states={
                                 'required': Eval('type_') != 'view',
                             },
                             depends=['type_'],
                             select=True)
    child = fields.One2Many('nereid.cms.menuitem',
                            'parent',
                            string='Child Menu Items')

    sequence = fields.Integer('Sequence', required=True, select=True)
    record = fields.Reference(
        'Record',
        selection='allowed_models',
        states={
            'required': Eval('type_') == 'record',
            'invisible': Eval('type_') != 'record',
        },
        depends=['type_'],
    )

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

        super(MenuItem, cls).__register__(module_name)

        table = TableHandler(cursor, cls, module_name)
        if table.column_exist('reference'):  # pragma: no cover
            table.not_null_action('unique_name', 'remove')

            # Delete the newly created record column
            table.drop_column('record')

            # Rename the reference column as record
            table.column_rename('reference', 'record', True)

            # The value of type depends on existence of record
            cursor.execute(*sql_table.update(
                columns=[sql_table.type_],
                values=['record'],
                where=(sql_table.record != None)  # noqa
            ))

    @classmethod
    def allowed_models(cls):
        return [
            (None, ''),
            ('nereid.cms.article.category', 'CMS Article Category'),
            ('nereid.cms.article', 'CMS Article'),
        ]

    @staticmethod
    def default_type_():
        return 'static'

    @staticmethod
    def default_target():
        return '_self'

    @staticmethod
    def default_sequence():
        return 10

    @staticmethod
    def default_active():
        return True

    @classmethod
    def __setup__(cls):
        super(MenuItem, cls).__setup__()
        cls._error_messages.update({
            'recursion_error':
            '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 get_rec_name(self, name):
        def _name(menuitem):
            if menuitem.parent:
                return _name(menuitem.parent) + ' / ' + menuitem.title
            else:
                return menuitem.title

        return _name(self)

    def get_menu_item(self, max_depth):
        """
        Return huge dictionary with serialized menu item

        {
            title: <display name>,
            target: <href target>,
            link: <url>,  # if type_ is `static`
            children: [   # direct children or record children
                <menu_item children>,
                ...
            ],
            record: <instance of record>  # if type_ is `record`
        }
        """
        res = {
            'title': self.title,
            'target': self.target,
            'type_': self.type_,
        }
        if self.type_ == 'static':
            res['link'] = self.link

        if self.type_ == 'record':
            res['record'] = self.record
            res['link'] = self.record.get_absolute_url()

        if max_depth:
            res['children'] = self.get_children(max_depth=max_depth - 1)

        if self.type_ == 'record' and not res.get('children') and max_depth:
            res['children'] = self.record.get_children(max_depth=max_depth - 1)
        return res

    def get_children(self, max_depth):
        """
        Return serialized menu_item for current menu_item children
        """
        children = self.search([('parent', '=', self.id),
                                ('active', '=', True)])
        return [
            child.get_menu_item(max_depth=max_depth - 1) for child in children
        ]

    def get_absolute_url(self, *args, **kwargs):
        """
        Return url for menu item
        """
        if self.type_ == 'record':
            return self.record.get_absolute_url(*args, **kwargs)
        return self.link
Example #14
0
class Article(Workflow, ModelSQL, ModelView, CMSMenuItemMixin):
    "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)
    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='allowed_models')
    description = fields.Text('Short Description')
    attributes = fields.One2Many('nereid.cms.article.attribute', 'article',
                                 'Attributes')
    categories = fields.Many2Many(
        'nereid.cms.category-article',
        'article',
        'category',
        'Categories',
    )
    content_type = fields.Selection('content_type_selection',
                                    'Content Type',
                                    required=True)
    # 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'),
            ('published', 'draft'),
            ('published', 'archived'),
            ('archived', 'draft'),
        ))
        cls._buttons.update({
            'archive': {
                'invisible': Eval('state') != 'published',
            },
            'publish': {
                'invisible': Eval('state').in_(['published', 'archived']),
            },
            'draft': {
                'invisible': Eval('state') == 'draft',
            }
        })

    @classmethod
    def content_type_selection(cls):
        """
        Returns a selection for content_type.
        """
        default_types = [('html', 'HTML'), ('plain', 'Plain Text')]

        if markdown:
            default_types.append(('markdown', 'Markdown'))
        if publish_parts:
            default_types.append(('rst', 'reStructured TeXT'))

        return default_types

    @classmethod
    def default_content_type(cls):
        """
        Default content_type.
        """
        return 'plain'

    def __html__(self):
        """
        Uses content_type field to generate html content.
        Concept from Jinja2's Markup class.
        """
        if self.content_type == 'rst':
            if publish_parts:
                res = publish_parts(self.content, writer_name='html')
                return res['html_body']
            self.raise_user_error(
                "`docutils` not installed, to render rst articles.")
        if self.content_type == 'markdown':
            if markdown:
                return markdown(self.content)
            self.raise_user_error(
                "`markdown` not installed, to render markdown article.")
        return self.content

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

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

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

    @classmethod
    def allowed_models(cls):
        MenuItem = Pool().get('nereid.cms.menuitem')

        return MenuItem.allowed_models()

    @staticmethod
    def default_active():
        return True

    @fields.depends('title', 'uri')
    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),
                ('state', '=', 'published'),
            ])
        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'

    def get_menu_item(self, max_depth):
        """
        Return huge dictionary with serialized article category for menu item

        {
            title: <display name>,
            link: <url>,
            record: <instance of record>  # if type_ is `record`
        }
        """
        return {
            'record': self,
            'title': self.title,
            'link': self.get_absolute_url(),
        }

    def atom_id(self):
        """
        Returns an atom ID for the article
        """
        return ('tag:' + request.nereid_website.name + ',' +
                self.publish_date + ':Article/' + str(self.id))

    def atom_publish_date(self):
        """
        Returns the article's publish date with timezone set as UTC
        """
        return pytz.utc.localize(
            datetime.combine(self.published_on, datetime.min.time()))

    def serialize(self, purpose=None):
        """
        Serialize Article records
        """
        if purpose == 'atom':
            # The keys in the dictionary returned are used by Werkzeug's
            # AtomFeed class.
            return {
                'id':
                self.atom_id(),
                'title':
                self.title,
                'author': (self.author.serialize(
                    purpose=purpose) if self.author else None),
                'content':
                self.content,
                'content_type':
                ('text' if self.content_type == 'plain' else 'html'),
                'link': {
                    'rel': 'alternate',
                    'type': 'text/html',
                    'href': self.get_absolute_url(external=True),
                },
                'category': [
                    category.serialize(purpose=purpose)
                    for category in self.categories
                ],
                'published':
                self.atom_publish_date(),
                'updated':
                self.write_date or self.atom_publish_date(),
            }
        elif hasattr(super(Article, self), 'serialize'):
            return super(Article, self).serialize(purpose=purpose)

    @classmethod
    @route('/article/all.atom')
    def atom_feed(cls):
        """
        Renders the atom feed for all articles.
        """
        feed = AtomFeed("All Articles",
                        feed_url=request.url,
                        url=request.host_url)
        for article in cls.search([('state', '=', 'published')]):
            feed.add(**article.serialize(purpose='atom'))

        return feed.get_response()
Example #15
0
class UIMenu(DeactivableMixin, sequence_ordered(), tree(separator=' / '),
             ModelSQL, ModelView):
    "UI menu"
    __name__ = 'ir.ui.menu'

    name = fields.Char('Menu', required=True, translate=True)
    childs = fields.One2Many('ir.ui.menu', 'parent', 'Children')
    parent = fields.Many2One('ir.ui.menu',
                             'Parent Menu',
                             select=True,
                             ondelete='CASCADE')
    groups = fields.Many2Many('ir.ui.menu-res.group', 'menu', 'group',
                              'Groups')
    complete_name = fields.Function(fields.Char('Complete Name'),
                                    'get_rec_name',
                                    searcher='search_rec_name')
    icon = fields.Selection('list_icons', 'Icon', translate=False)
    action = fields.Function(fields.Reference(
        'Action',
        selection=[
            ('', ''),
            ('ir.action.report', 'ir.action.report'),
            ('ir.action.act_window', 'ir.action.act_window'),
            ('ir.action.wizard', 'ir.action.wizard'),
            ('ir.action.url', 'ir.action.url'),
        ],
        translate=False),
                             'get_action',
                             setter='set_action')
    action_keywords = fields.One2Many('ir.action.keyword', 'model',
                                      'Action Keywords')
    favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')

    @classmethod
    def order_complete_name(cls, tables):
        return cls.name.convert_order('name', tables, cls)

    @staticmethod
    def default_icon():
        return 'tryton-folder'

    @staticmethod
    def default_sequence():
        return 10

    @staticmethod
    def list_icons():
        pool = Pool()
        Icon = pool.get('ir.ui.icon')
        return sorted(CLIENT_ICONS + [(name, name)
                                      for _, name in Icon.list_icons()])

    @classmethod
    def search_global(cls, text):
        # TODO improve search clause
        for record in cls.search([
            ('rec_name', 'ilike', '%%%s%%' % text),
        ]):
            if record.action:
                yield record, record.rec_name, record.icon

    @classmethod
    def search(cls,
               domain,
               offset=0,
               limit=None,
               order=None,
               count=False,
               query=False):
        menus = super(UIMenu, cls).search(domain,
                                          offset=offset,
                                          limit=limit,
                                          order=order,
                                          count=False,
                                          query=query)
        if query:
            return menus

        if menus:
            parent_ids = {x.parent.id for x in menus if x.parent}
            parents = set()
            for sub_parent_ids in grouped_slice(parent_ids):
                parents.update(
                    cls.search([
                        ('id', 'in', list(sub_parent_ids)),
                    ]))
            # Re-browse to avoid side-cache access
            menus = cls.browse([
                x.id for x in menus
                if (x.parent and x.parent in parents) or not x.parent
            ])

        if count:
            return len(menus)
        return menus

    @classmethod
    def get_action(cls, menus, name):
        pool = Pool()
        actions = dict((m.id, None) for m in menus)
        with Transaction().set_context(active_test=False):
            menus = cls.browse(menus)
        action_keywords = sum((list(m.action_keywords) for m in menus), [])

        def action_type(keyword):
            return keyword.action.type

        action_keywords.sort(key=action_type)
        for type, action_keywords in groupby(action_keywords, key=action_type):
            action_keywords = list(action_keywords)
            for action_keyword in action_keywords:
                model = action_keyword.model
                actions[model.id] = '%s,-1' % type

            Action = pool.get(type)
            action2keyword = {k.action.id: k for k in action_keywords}
            with Transaction().set_context(active_test=False):
                factions = Action.search([
                    ('action', 'in', list(action2keyword.keys())),
                ])
            for action in factions:
                model = action2keyword[action.id].model
                actions[model.id] = str(action)
        return actions

    @classmethod
    def set_action(cls, menus, name, value):
        pool = Pool()
        ActionKeyword = pool.get('ir.action.keyword')
        action_keywords = []
        transaction = Transaction()
        for i in range(0, len(menus), transaction.database.IN_MAX):
            sub_menus = menus[i:i + transaction.database.IN_MAX]
            action_keywords += ActionKeyword.search([
                ('keyword', '=', 'tree_open'),
                ('model', 'in', [str(menu) for menu in sub_menus]),
            ])
        if action_keywords:
            with Transaction().set_context(_timestamp=False):
                ActionKeyword.delete(action_keywords)
        if not value:
            return
        if isinstance(value, str):
            action_type, action_id = value.split(',')
        else:
            action_type, action_id = value
        if int(action_id) <= 0:
            return
        Action = pool.get(action_type)
        action = Action(int(action_id))
        to_create = []
        for menu in menus:
            with Transaction().set_context(_timestamp=False):
                to_create.append({
                    'keyword': 'tree_open',
                    'model': str(menu),
                    'action': action.action.id,
                })
        if to_create:
            ActionKeyword.create(to_create)

    @classmethod
    def get_favorite(cls, menus, name):
        pool = Pool()
        Favorite = pool.get('ir.ui.menu.favorite')
        user = Transaction().user
        favorites = Favorite.search([
            ('menu', 'in', [m.id for m in menus]),
            ('user', '=', user),
        ])
        menu2favorite = dict(
            (m.id, False if m.action else None) for m in menus)
        menu2favorite.update(dict((f.menu.id, True) for f in favorites))
        return menu2favorite
Example #16
0
class InventoryLine(ModelSQL, ModelView):
    'Stock Inventory Line'
    __name__ = 'stock.inventory.line'
    _states = {
        'readonly': Eval('inventory_state') != 'draft',
    }
    _depends = ['inventory_state']

    product = fields.Many2One('product.product',
                              'Product',
                              required=True,
                              domain=[
                                  ('type', '=', 'goods'),
                                  ('consumable', '=', False),
                              ],
                              states=_states,
                              depends=_depends)
    uom = fields.Function(fields.Many2One('product.uom', 'UOM'), 'get_uom')
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
                                  'get_unit_digits')
    expected_quantity = fields.Float('Expected Quantity',
                                     required=True,
                                     digits=(16, Eval('unit_digits', 2)),
                                     readonly=True,
                                     states={
                                         'invisible': Eval('id', -1) < 0,
                                     },
                                     depends=['unit_digits'])
    quantity = fields.Float('Quantity',
                            digits=(16, Eval('unit_digits', 2)),
                            states=_states,
                            depends=['unit_digits'] + _depends)
    moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
    inventory = fields.Many2One('stock.inventory',
                                'Inventory',
                                required=True,
                                ondelete='CASCADE',
                                states={
                                    'readonly':
                                    _states['readonly']
                                    & Bool(Eval('inventory')),
                                },
                                depends=_depends)
    inventory_state = fields.Function(
        fields.Selection(INVENTORY_STATES, 'Inventory State'),
        'on_change_with_inventory_state')

    @classmethod
    def __setup__(cls):
        super(InventoryLine, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints += [
            ('check_line_qty_pos', Check(t, t.quantity >= 0),
             'Line quantity must be positive.'),
        ]
        cls._order.insert(0, ('product', 'ASC'))
        cls._error_messages.update({
            'missing_empty_quantity':
            ('An option for empty quantity is '
             'missing for inventory "%(inventory)s".'),
            'delete_cancel_draft':
            ('The line "%(line)s" must be on '
             'canceled or draft inventory to be deleted.'),
        })

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        pool = Pool()
        Move = pool.get('stock.move')
        sql_table = cls.__table__()
        move_table = Move.__table__()

        super(InventoryLine, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)

        # Migration from 3.0: use Move origin
        if table.column_exist('move'):
            cursor.execute(*sql_table.select(
                sql_table.id, sql_table.move, where=sql_table.move != Null))
            for line_id, move_id in cursor.fetchall():
                cursor.execute(*move_table.update(
                    columns=[move_table.origin],
                    values=['%s,%s' % (cls.__name__, line_id)],
                    where=move_table.id == move_id))
            table.drop_column('move')

        # Migration from 4.6: drop required on quantity
        table.not_null_action('quantity', action='remove')

    @staticmethod
    def default_unit_digits():
        return 2

    @staticmethod
    def default_expected_quantity():
        return 0.

    @fields.depends('product')
    def on_change_product(self):
        self.unit_digits = 2
        if self.product:
            self.uom = self.product.default_uom
            self.unit_digits = self.product.default_uom.digits

    @fields.depends('inventory', '_parent_inventory.state')
    def on_change_with_inventory_state(self, name=None):
        if self.inventory:
            return self.inventory.state
        return 'draft'

    def get_rec_name(self, name):
        return self.product.rec_name

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('product.rec_name', ) + tuple(clause[1:])]

    def get_uom(self, name):
        return self.product.default_uom.id

    def get_unit_digits(self, name):
        return self.product.default_uom.digits

    @property
    def unique_key(self):
        key = []
        for fname in self.inventory.grouping():
            value = getattr(self, fname)
            if isinstance(value, Model):
                value = value.id
            key.append(value)
        return tuple(key)

    @classmethod
    def cancel_move(cls, lines):
        Move = Pool().get('stock.move')
        moves = [m for l in lines for m in l.moves if l.moves]
        Move.cancel(moves)
        Move.delete(moves)

    def get_move(self):
        '''
        Return Move instance for the inventory line
        '''
        pool = Pool()
        Move = pool.get('stock.move')
        Uom = pool.get('product.uom')

        qty = self.quantity
        if qty is None:
            if self.inventory.empty_quantity is None:
                self.raise_user_error('missing_empty_quantity', {
                    'inventory': self.inventory.rec_name,
                })
            if self.inventory.empty_quantity == 'keep':
                return
            else:
                qty = 0.0

        delta_qty = Uom.compute_qty(self.uom, self.expected_quantity - qty,
                                    self.uom)
        if delta_qty == 0.0:
            return
        from_location = self.inventory.location
        to_location = self.inventory.lost_found
        if delta_qty < 0:
            (from_location, to_location, delta_qty) = \
                (to_location, from_location, -delta_qty)

        return Move(
            from_location=from_location,
            to_location=to_location,
            quantity=delta_qty,
            product=self.product,
            uom=self.uom,
            company=self.inventory.company,
            effective_date=self.inventory.date,
            origin=self,
        )

    def update_values4complete(self, quantity):
        '''
        Return update values to complete inventory
        '''
        values = {}
        # if nothing changed, no update
        if self.quantity == self.expected_quantity == quantity:
            return values
        values['expected_quantity'] = quantity
        return values

    @classmethod
    def create_values4complete(cls, inventory, quantity):
        '''
        Return create values to complete inventory
        '''
        return {
            'inventory': inventory.id,
            'expected_quantity': quantity,
        }

    @classmethod
    def delete(cls, lines):
        for line in lines:
            if line.inventory_state not in {'cancel', 'draft'}:
                cls.raise_user_error('delete_cancel_draft', {
                    'line': line.rec_name,
                })
        super(InventoryLine, cls).delete(lines)
Example #17
0
class MoveLine:
    __metaclass__ = PoolMeta
    __name__ = 'account.move.line'
    payment_amount = fields.Function(fields.Numeric(
        'Payment Amount',
        digits=(16,
                If(Bool(Eval('second_currency_digits')),
                   Eval('second_currency_digits', 2),
                   Eval('currency_digits', 2))),
        states={
            'invisible': ~Eval('payment_kind'),
        },
        depends=['payment_kind', 'second_currency_digits', 'currency_digits']),
                                     'get_payment_amount',
                                     searcher='search_payment_amount')
    payments = fields.One2Many('account.payment',
                               'line',
                               'Payments',
                               readonly=True,
                               states={
                                   'invisible': ~Eval('payment_kind'),
                               },
                               depends=['payment_kind'])
    payment_kind = fields.Function(fields.Selection([
        (None, ''),
    ] + KINDS, 'Payment Kind'),
                                   'get_payment_kind',
                                   searcher='search_payment_kind')
    payment_blocked = fields.Boolean('Blocked', readonly=True)
    payment_direct_debit = fields.Boolean(
        "Direct Debit",
        states={
            'invisible':
            ~((Eval('payment_kind') == 'payable')
              & ((Eval('credit', 0) > 0) | (Eval('debit', 0) < 0))),
        },
        depends=['payment_kind', 'debit', 'credit'],
        help="Check if the line will be paid by direct debit.")

    @classmethod
    def __setup__(cls):
        super(MoveLine, cls).__setup__()
        cls._buttons.update({
            'pay': {
                'invisible': ~Eval('payment_kind').in_(dict(KINDS).keys()),
            },
            'payment_block': {
                'invisible': Eval('payment_blocked', False),
            },
            'payment_unblock': {
                'invisible': ~Eval('payment_blocked', False),
            },
        })
        cls._check_modify_exclude.update(
            ['payment_blocked', 'payment_direct_debit'])

    @classmethod
    def default_payment_direct_debit(cls):
        return False

    @classmethod
    def get_payment_amount(cls, lines, name):
        amounts = {}
        for line in lines:
            if line.account.kind not in ('payable', 'receivable'):
                amounts[line.id] = None
                continue
            if line.second_currency:
                amount = abs(line.amount_second_currency)
            else:
                amount = abs(line.credit - line.debit)

            for payment in line.payments:
                if payment.state != 'failed':
                    amount -= payment.amount

            amounts[line.id] = amount
        return amounts

    @classmethod
    def search_payment_amount(cls, name, clause):
        pool = Pool()
        Payment = pool.get('account.payment')
        Account = pool.get('account.account')
        _, operator, value = clause
        Operator = fields.SQL_OPERATORS[operator]
        table = cls.__table__()
        payment = Payment.__table__()
        account = Account.__table__()

        payment_amount = Sum(Coalesce(payment.amount, 0))
        main_amount = Abs(table.credit - table.debit) - payment_amount
        second_amount = Abs(table.amount_second_currency) - payment_amount
        amount = Case((table.second_currency == Null, main_amount),
                      else_=second_amount)
        value = cls.payment_amount.sql_format(value)

        query = table.join(
            payment,
            type_='LEFT',
            condition=(table.id == payment.line) &
            (payment.state != 'failed')).join(
                account, condition=table.account == account.id).select(
                    table.id,
                    where=account.kind.in_(['payable', 'receivable']),
                    group_by=(table.id, account.kind, table.second_currency),
                    having=Operator(amount, value))
        return [('id', 'in', query)]

    def get_payment_kind(self, name):
        return self.account.kind if self.account.kind in dict(KINDS) else None

    @classmethod
    def search_payment_kind(cls, name, clause):
        return [('account.kind', ) + tuple(clause[1:])]

    @classmethod
    def default_payment_blocked(cls):
        return False

    @classmethod
    def copy(cls, lines, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('payments', None)
        return super(MoveLine, cls).copy(lines, default=default)

    @classmethod
    @ModelView.button_action('account_payment.act_pay_line')
    def pay(cls, lines):
        pass

    @classmethod
    @ModelView.button
    def payment_block(cls, lines):
        pool = Pool()
        Payment = pool.get('account.payment')

        cls.write(lines, {
            'payment_blocked': True,
        })
        draft_payments = [
            p for l in lines for p in l.payments if p.state == 'draft'
        ]
        if draft_payments:
            Payment.delete(draft_payments)

    @classmethod
    @ModelView.button
    def payment_unblock(cls, lines):
        cls.write(lines, {
            'payment_blocked': False,
        })
Example #18
0
class ProductSaleChannelListing(ModelSQL, ModelView):
    '''Product - Sale Channel
    This model keeps a record of a product's association with Sale Channels.
    A product can be listed on multiple marketplaces
    '''
    __name__ = 'product.product.channel_listing'

    # TODO: Only show channels where this ability is there. For example
    # showing a manual channel is pretty much useless
    channel = fields.Many2One('sale.channel',
                              'Sale Channel',
                              domain=[('source', '!=', 'manual')],
                              required=True,
                              select=True,
                              ondelete='RESTRICT')
    product = fields.Many2One('product.product',
                              'Product',
                              select=True,
                              states={'required': Eval('state') == 'active'},
                              ondelete='CASCADE',
                              depends=['state'])
    product_identifier = fields.Char("Product Identifier",
                                     select=True,
                                     required=True)
    state = fields.Selection([
        ('active', 'Active'),
        ('disabled', 'Disabled'),
    ],
                             'State',
                             required=True,
                             select=True)
    channel_source = fields.Function(fields.Char("Channel Source"),
                                     getter="get_channel_source")

    quantity = fields.Function(
        fields.Float('Quantity',
                     digits=(16, Eval('unit_digits', 2)),
                     depends=['unit_digits']), 'get_availability_fields')
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
                                  'get_unit_digits')
    availability_type_used = fields.Function(
        fields.Selection([
            ('bucket', 'Bucket'),
            ('quantity', 'Quantity'),
            ('infinite', 'Infinite'),
        ], 'Type'), 'get_availability_fields')
    availability_used = fields.Function(
        fields.Selection([
            ('in_stock', 'In-Stock'),
            ('out_of_stock', 'Out Of Stock'),
        ],
                         'Availability',
                         states={
                             'invisible':
                             ~Bool(Eval('availability_type_used') == 'bucket')
                         },
                         depends=['availability_type_used']),
        'get_availability_fields')
    listing_url = fields.Function(fields.Char('Listing URL'),
                                  'get_listing_url')

    @classmethod
    def search_rec_name(cls, name, clause):
        return [
            'OR',
            ('product', ) + tuple(clause[1:]),
            ('product_identifier', ) + tuple(clause[1:]),
        ]

    @classmethod
    def get_unit_digits(cls, records, name):
        result = {
            r.id: r.product.default_uom.digits if r.product else 2
            for r in records
        }

        return result

    @classmethod
    def get_listing_url(cls, records, name):
        """
        Downstream modules should implement this function
        and return a valid url
        """
        return dict.fromkeys([r.id for r in records])

    @classmethod
    def get_availability_fields(cls, listings, names):
        listing_ids = map(int, listings)
        values = defaultdict(lambda: dict.fromkeys(listing_ids, None))
        for name in names:
            # Just call the default dict once so all fields have values
            # even if product is absent
            values[name]

        for listing in listings:
            if listing.product:
                availability = listing.get_availability()
                values['availability_type_used'][listing.id] = \
                    availability['type']
                values['availability_used'][listing.id] = availability.get(
                    'value')
                values['quantity'][listing.id] = availability.get('quantity')
        return values

    @classmethod
    def get_channel_source(cls, records, name):
        result = {r.id: r.channel and r.channel.source for r in records}

        return result

    @fields.depends('channel')
    def on_change_with_channel_source(self, name=None):
        return self.channel and self.channel.source

    @classmethod
    def __setup__(cls):
        '''
        Setup the class and define constraints
        '''
        super(ProductSaleChannelListing, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints += [
            ('channel_product_identifier_uniq',
             Unique(table, table.channel, table.product_identifier),
             'This external product is already mapped with same channel.')
        ]

        cls._buttons.update({
            'export_inventory_button': {},
        })

    @staticmethod
    def default_state():
        return 'active'

    @classmethod
    def create_from(cls, channel, product_data):
        """
        Create a listing for the product from channel and data
        """
        raise NotImplementedError(
            "create_from is not implemented in channel listing for %s channels"
            % channel.source)

    @classmethod
    @ModelView.button
    def export_inventory_button(cls, listings):
        return cls.export_bulk_inventory(listings)

    def export_inventory(self):
        """
        Export listing.product inventory to listing.channel

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement exporting or call
        super to delegate.
        """
        raise NotImplementedError(
            "Export inventory is not implemented for %s channels" %
            self.channel.source)

    @classmethod
    def export_bulk_inventory(cls, listings):
        """
        Export listing.product inventory to listing.channel in bulk

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement bulk exporting for
        respective channels.
        Default behaviour is to export inventory individually.
        """
        for listing in listings:
            listing.export_inventory()

    def import_product_image(self):
        """
        Import specific product image from external channel based on product
        identifier.

        Since external channels are implemented by downstream modules, it is
        the responsibility of those channels to implement importing or call
        super to delegate.
        """
        raise NotImplementedError(
            "Method import_product_image is not implemented for %s channel yet"
            % self.source)

    def get_availability_context(self):
        """
        Allow overriding the context used to compute availability of
        products.
        """
        return {
            'locations': [self.channel.warehouse.id],
        }

    def get_availability(self):
        """
        Return the availability of the product for this listing
        """
        Product = Pool().get('product.product')

        with Transaction().set_context(**self.get_availability_context()):
            rv = {'type': 'bucket', 'value': None, 'quantity': None}
            if self.product:
                product = Product(self.product.id)
                rv['quantity'] = product.quantity
                if rv['quantity'] > 0:
                    rv['value'] = 'in_stock'
                else:
                    rv['value'] = 'out_of_stock'
            return rv
Example #19
0
class SaleOpportunity(Workflow, ModelSQL, ModelView):
    'Sale Opportunity'
    __name__ = "sale.opportunity"
    '''horario = fields.Many2One('training.horario',
    	'Horario', 
    	states={
            'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']),
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
            }, depends=['state'])'''
    medio = fields.Selection(
        [
            ('Facebook', 'Facebook'),
            ('Google', 'Google'),
            ('Volante', 'Volante'),
            ('Stand', 'Stand'),
            ('Manta', 'Manta'),
            ('Muppy', 'Muppy'),
            ('Otro', 'Otro'),
        ],
        'Medio',
        sort=False,
        states={
            'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']),
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        depends=['state'])
    subscriptions = fields.One2Many('sale.subscription', 'origin',
                                    'Suscripciones')

    meetings = fields.One2Many('sale.meeting',
                               'opportunity',
                               'Seguimiento',
                               states=_STATES_START,
                               depends=_DEPENDS_START)

    def _get_sale_opportunity(self):
        '''
        Return subscription for an opportunity
        '''

        pool = Pool()
        Subscription = pool.get('sale.subscription')
        Date = pool.get('ir.date')
        Recurrence = pool.get('sale.subscription.recurrence.rule.set')
        Party = pool.get('party.party')
        recurrences = Recurrence.search([])
        recurrence = recurrences[0].id
        parties = Party.search(['id', '=', self.party.id])
        for party in parties:
            Party.write([party], {'estudiante': True})

        return Subscription(
            description=self.description,
            party=self.party,
            estudiante=self.party,
            payment_term=self.payment_term,
            company=self.company,
            invoice_address=self.address,
            currency=self.company.currency,
            start_date=Date.today(),
            invoice_recurrence=recurrence,
            #horario = self.horario,
            asesor=self.employee,
            medio=self.medio,
            origin=self,
        )

    def create_sale(self):
        '''
        Create a subscription for the opportunity and return the subscription
        '''
        subscription = self._get_sale_opportunity()
        sale_lines = []
        for line in self.lines:
            sale_lines.append(line.get_sale_line(subscription))
        subscription.lines = sale_lines
        return subscription

    @classmethod
    @ModelView.button
    @Workflow.transition('converted')
    def convert(cls, opportunities):
        pool = Pool()
        Subscription = pool.get('sale.subscription')
        subscriptions = [
            o.create_sale() for o in opportunities if not o.subscriptions
        ]
        Subscription.save(subscriptions)

    @staticmethod
    def _sale_won_states():
        return ['running', 'closed']

    @staticmethod
    def _sale_lost_states():
        return ['canceled']

    def is_won(self):
        sale_won_states = self._sale_won_states()
        sale_lost_states = self._sale_lost_states()
        end_states = sale_won_states + sale_lost_states
        return (self.subscriptions
                and all(s.state in end_states for s in self.subscriptions)
                and any(s.state in sale_won_states
                        for s in self.subscriptions))

    def is_lost(self):
        sale_lost_states = self._sale_lost_states()
        return (self.subscriptions and all(s.state in sale_lost_states
                                           for s in self.subscriptions))

    @classmethod
    def process(cls, opportunities):
        won = []
        lost = []
        converted = []
        for opportunity in opportunities:
            sale_amount = opportunity.sale_amount
            if opportunity.amount != sale_amount:
                opportunity.amount = sale_amount
            if opportunity.is_won():
                won.append(opportunity)
            elif opportunity.is_lost():
                lost.append(opportunity)
            elif (opportunity.state != 'converted'
                  and opportunity.subscriptions):
                converted.append(opportunity)
        cls.save(opportunities)
        if won:
            cls.won(won)
        if lost:
            cls.lost(lost)
        if converted:
            cls.convert(converted)

    @property
    def is_forecast(self):
        pool = Pool()
        Date = pool.get('ir.date')
        today = Date.today()
        return self.end_date or datetime.date.max > today

    @classmethod
    @Workflow.transition('won')
    def won(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        cls.write(filter(lambda o: o.is_forecast, opportunities), {
            'end_date': Date.today(),
            'state': 'won',
        })

    @property
    def sale_amount(self):

        pool = Pool()
        Currency = pool.get('currency.currency')

        if not self.subscriptions:
            #print "NO SUBSCRIPTIONS"
            return

        sale_lost_states = self._sale_lost_states()
        amount = 0
        for subscription in self.subscriptions:
            if subscription.state not in sale_lost_states:
                amount += Currency.compute(subscription.currency,
                                           subscription.amount, self.currency)
        return amount

    @classmethod
    def default_employee(cls):
        User = Pool().get('res.user')
        employee_id = None
        if Transaction().context.get('employee'):
            employee_id = Transaction().context['employee']
        else:
            user = User(Transaction().user)
            if user.employee:
                employee_id = user.employee.id
        if employee_id:
            return employee_id

    @fields.depends('party')
    def on_change_party(self):
        self.invoice_address = None
        if self.party:
            self.address = self.party.address_get(type='invoice')
            self.payment_term = self.party.customer_payment_term
Example #20
0
class PartyIdentifier(ModelSQL, ModelView):
    'Party Identifier'
    __name__ = 'party.identifier'
    _rec_name = 'code'
    party = fields.Many2One('party.party', 'Party', ondelete='CASCADE',
        required=True, select=True)
    type = fields.Selection('get_types', 'Type')
    code = fields.Char('Code', required=True)

    @classmethod
    def __setup__(cls):
        super(PartyIdentifier, cls).__setup__()
        cls._error_messages.update({
                'invalid_vat': ('Invalid VAT number "%(code)s" '
                    'on party "%(party)s".'),
                })

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        Party = pool.get('party.party')
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().cursor
        party = Party.__table__()

        super(PartyIdentifier, cls).__register__(module_name)

        party_h = TableHandler(cursor, Party, module_name)
        if (party_h.column_exist('vat_number')
                and party_h.column_exist('vat_country')):
            identifiers = []
            cursor.execute(*party.select(
                    party.id, party.vat_number, party.vat_country,
                    where=(party.vat_number != Null)
                    | (party.vat_country != Null)))
            for party_id, number, country in cursor.fetchall():
                code = (country or '') + (number or '')
                if not code:
                    continue
                type = None
                if vat.is_valid(code):
                    type = 'eu_vat'
                identifiers.append(
                    cls(party=party_id, code=code, type=type))
            cls.save(identifiers)
            party_h.drop_column('vat_number')
            party_h.drop_column('vat_country')

    @classmethod
    def get_types(cls):
        return [
            (None, ''),
            ('eu_vat', 'VAT'),
            ]

    @fields.depends('type', 'code')
    def on_change_with_code(self):
        if self.type == 'eu_vat':
            try:
                return vat.compact(self.code)
            except stdnum.exceptions.ValidationError:
                pass
        return self.code

    @classmethod
    def validate(cls, identifiers):
        super(PartyIdentifier, cls).validate(identifiers)
        for identifier in identifiers:
            identifier.check_code()

    def check_code(self):
        if self.type == 'eu_vat':
            if not vat.is_valid(self.code):
                self.raise_user_error('invalid_vat', {
                        'code': self.code,
                        'party': self.party.rec_name,
                        })
Example #21
0
class Carrier(ModelSQL, ModelView):
    'Carrier'
    __name__ = 'carrier'
    party = fields.Many2One('party.party',
                            'Party',
                            required=True,
                            ondelete='CASCADE',
                            help="The party which represents the carrier.")
    carrier_product = fields.Many2One(
        'product.product',
        'Carrier Product',
        required=True,
        domain=[
            ('type', '=', 'service'),
            ('template.type', '=', 'service'),
        ],
        help="The product to invoice the carrier service.")
    carrier_cost_method = fields.Selection(
        [
            ('product', 'Product Price'),
        ],
        'Carrier Cost Method',
        required=True,
        help='Method to compute carrier cost.')

    @staticmethod
    def default_carrier_cost_method():
        return 'product'

    def get_rec_name(self, name):
        return '%s - %s' % (self.party.rec_name, self.carrier_product.rec_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,
            ('party.rec_name', ) + tuple(clause[1:]),
            ('carrier_product.rec_name', ) + tuple(clause[1:]),
        ]

    def get_sale_price(self):
        'Compute carrier sale price with currency'
        User = Pool().get('res.user')
        if self.carrier_cost_method == 'product':
            user = User(Transaction().user)
            return self.carrier_product.list_price, user.company.currency.id
        return 0, None

    def get_purchase_price(self):
        'Compute carrier purchase price with currency'
        User = Pool().get('res.user')
        if self.carrier_cost_method == 'product':
            user = User(Transaction().user)
            return self.carrier_product.cost_price, user.company.currency.id
        return 0, None

    @classmethod
    def create(cls, *args, **kwargs):
        pool = Pool()
        CarrierSelection = pool.get('carrier.selection')
        carriers = super(Carrier, cls).create(*args, **kwargs)
        CarrierSelection._get_carriers_cache.clear()
        return carriers

    @classmethod
    def delete(cls, *args, **kwargs):
        pool = Pool()
        CarrierSelection = pool.get('carrier.selection')
        super(Carrier, cls).delete(*args, **kwargs)
        CarrierSelection._get_carriers_cache.clear()
Example #22
0
class ProductProductAttribute(ModelSQL, ModelView):
    "Product's Product Attribute"
    __name__ = 'product.product.attribute'

    product = fields.Many2One("product.product",
                              "Product",
                              select=True,
                              required=True,
                              ondelete='CASCADE')

    attribute = fields.Many2One("product.attribute",
                                "Attribute",
                                required=True,
                                select=True,
                                domain=[('sets', '=', Eval('attribute_set'))],
                                depends=['attribute_set'],
                                ondelete='RESTRICT')

    attribute_type = fields.Function(
        fields.Selection(ATTRIBUTE_TYPES, "Attribute Type"),
        'get_attribute_type')

    attribute_set = fields.Function(
        fields.Many2One("product.attribute.set", "Attribute Set"),
        'get_attribute_set')

    value = fields.Function(fields.Char('Attribute Value'), getter='get_value')

    value_char = fields.Char("Value Char",
                             states={
                                 'required': Eval('attribute_type') == 'char',
                                 'invisible':
                                 ~(Eval('attribute_type') == 'char'),
                             },
                             depends=['attribute_type'])
    value_numeric = fields.Numeric("Value Numeric",
                                   states={
                                       'required':
                                       Eval('attribute_type') == 'numeric',
                                       'invisible':
                                       ~(Eval('attribute_type') == 'numeric'),
                                   },
                                   depends=['attribute_type'])
    value_float = fields.Float("Value Float",
                               states={
                                   'required':
                                   Eval('attribute_type') == 'float',
                                   'invisible':
                                   ~(Eval('attribute_type') == 'float'),
                               },
                               depends=['attribute_type'])

    value_selection = fields.Many2One(
        "product.attribute.selection_option",
        "Value Selection",
        domain=[('attribute', '=', Eval('attribute'))],
        states={
            'required': Eval('attribute_type') == 'selection',
            'invisible': ~(Eval('attribute_type') == 'selection'),
        },
        depends=['attribute', 'attribute_type'],
        ondelete='RESTRICT')

    value_boolean = fields.Boolean("Value Boolean",
                                   states={
                                       'required':
                                       Eval('attribute_type') == 'boolean',
                                       'invisible':
                                       ~(Eval('attribute_type') == 'boolean'),
                                   },
                                   depends=['attribute_type'])
    value_integer = fields.Integer("Value Integer",
                                   states={
                                       'required':
                                       Eval('attribute_type') == 'integer',
                                       'invisible':
                                       ~(Eval('attribute_type') == 'integer'),
                                   },
                                   depends=['attribute_type'])
    value_date = fields.Date("Value Date",
                             states={
                                 'required': Eval('attribute_type') == 'date',
                                 'invisible':
                                 ~(Eval('attribute_type') == 'date'),
                             },
                             depends=['attribute_type'])
    value_datetime = fields.DateTime(
        "Value Datetime",
        states={
            'required': Eval('attribute_type') == 'datetime',
            'invisible': ~(Eval('attribute_type') == 'datetime'),
        },
        depends=['attribute_type'])

    @fields.depends('attribute')
    def on_change_attribute(self):
        self.attribute_type = self.attribute and self.attribute.type_ or None

    def get_attribute_type(self, name=None):
        """
        Returns type of attribute
        """
        return self.attribute.type_

    def get_value(self, name=None):
        """
        Consolidated method to return attribute value
        """
        if self.attribute_type == 'selection':
            return self.value_selection.name
        if self.attribute_type == 'datetime':
            # XXX: Localize to the timezone in context
            return self.value_datetime.strftime("%Y-%m-%d %H:%M:%S")
        if self.attribute_type == 'date':
            return datetime.combine(self.value_date, time()). \
                strftime("%Y-%m-%d")
        else:
            return unicode(getattr(self, 'value_' + self.attribute_type))

    def get_attribute_set(self, name=None):
        """
        Returns attribute set for corresponding product's template
        """
        if self.product and self.product.template and \
                self.product.template.attribute_set:
            return self.product.template.attribute_set.id

    @fields.depends('product')
    def on_change_product(self):
        if self.product and self.product.template.attribute_set:
            self.attribute_set = self.product.template.attribute_set.id
Example #23
0
class DocumentInvoice(ModelWorkflow, ModelSQL, ModelView):
    "Documents (Invoice) "
    _name = 'ekd.document.head.invoice'
    _description = __doc__
    #_table='documents_document_stock'
    _inherits = {'ekd.document': 'document'}

    document = fields.Many2One('ekd.document',
                               'Document',
                               required=True,
                               ondelete='CASCADE')

    direct_document = fields.Selection([
        ('invoice_revenue', 'For Customer'),
        ('invoice_expense', 'From Supplier'),
    ], 'Direct Document')

    type_product = fields.Selection([
        ('fixed_assets', 'Fixed Assets'),
        ('intangible', 'Intangible assets'),
        ('material', 'Material'),
        ('goods', 'Goods'),
    ], 'Type Product')

    template_invoice = fields.Function(fields.Many2One(
        'ekd.document.template',
        'Document Name',
        domain=[('type_account', '=',
                 Eval('direct_document',
                      Get(Eval('context', {}), 'direct_document')))]),
                                       'get_fields',
                                       setter='set_fields')
    number_doc = fields.Function(fields.Char(
        'Number Document',
        states={
            'invisible':
            And(Not(Bool(Eval('number_doc'))),
                Not(Equal(Eval('direct_document', ''), 'invoice_expense')))
        }),
                                 'get_fields',
                                 setter='set_fields',
                                 searcher='template_search')
    from_to_party = fields.Function(fields.Many2One('party.party', 'Party'),
                                    'get_fields',
                                    setter='set_fields')

    lines = fields.One2Many('ekd.document.line.product',
                            'invoice',
                            'Lines',
                            context={'type_product': Eval('type_product')})
    amount_doc = fields.Function(fields.Numeric(
        'Amount', digits=(16, Eval('currency_digits', 2))),
                                 'get_fields',
                                 setter='set_fields')
    amount_tax = fields.Function(
        fields.Numeric('Amount Tax', digits=(16, Eval('currency_digits', 2))),
        'get_fields')
    currency = fields.Many2One('currency.currency', 'Currency')
    currency_digits = fields.Function(
        fields.Integer('Currency Digits', on_change_with=['currency']),
        'get_currency_digits')
    payment_term = fields.Many2One('ekd.document.payment_term',
                                   'Payment Term',
                                   required=True,
                                   states=_STATES)
    form_payment = fields.Selection([
        ('cash', 'Cash payment'),
        ('bank', 'Bank payment'),
        ('post', 'Post payment'),
        ('internet', 'Internet payment'),
    ], 'Form of payment')
    kind_payment = fields.Selection([
        ('postpay', 'Postpay'),
        ('prepayment', 'Prepayment'),
        ('advance', 'Down payment'),
    ], 'Kind of payment')
    type_tax = fields.Selection([
        ('not_vat', 'Not VAT'),
        ('including', 'Including'),
        ('over_amount', 'Over Amount'),
    ], 'Type Compute Tax')

    state_doc = fields.Function(fields.Selection(_STATE_INVOICE,
                                                 required=True,
                                                 readonly=True,
                                                 string='State'),
                                'get_fields',
                                setter='set_fields')
    deleting = fields.Boolean('Flag deleting', readonly=True)

    def __init__(self):
        super(DocumentInvoice, self).__init__()
        self._rpc.update({
            'button_add_number': True,
            'button_draft': True,
            'button_to_pay': True,
            'button_part_paid': True,
            'button_paid': True,
            'button_received': True,
            'button_cancel': True,
            'button_restore': True,
            'button_print': True,
            'button_send': True,
            'button_post': True,
            'button_obtained': True,
            'button_delivered': True,
        })

        self._order.insert(0, ('date_document', 'ASC'))
        self._order.insert(1, ('template', 'ASC'))

    def default_state_doc(self):
        return Transaction().context.get('state') or 'draft'

    def default_type_product(self):
        return Transaction().context.get('type_product') or 'material'

    def default_from_to_party_doc(self):
        return Transaction().context.get('from_to_party') or False

    def default_direct_document(self):
        return Transaction().context.get(
            'direct_document') or 'invoice_revenue'

    def default_template_invoice(self):
        context = Transaction().context
        if context.get('template_invoice', False):
            return context.get('template_invoice')
        else:
            template_obj = self.pool.get('ekd.document.template')
            template_ids = template_obj.search(
                [('type_account', '=', context.get('direct_document'))],
                order=[('sequence', 'ASC')])
            if len(template_ids) > 0:
                return template_ids[0]

    def default_currency(self):
        company_obj = self.pool.get('company.company')
        company = company_obj.browse(Transaction().context['company'])
        return company.currency.id

    def default_currency_digits(self):
        company_obj = self.pool.get('company.company')
        context = Transaction().context
        if context.get('company'):
            company = company_obj.browse(context['company'])
            return company.currency.digits
        return 2

    def get_currency_digits(self, ids, name):
        res = {}
        for line in self.browse(ids):
            res[line.id] = line.currency and line.currency.digits or 2
        return res

    def default_payment_term(self):
        payment_term_obj = self.pool.get('ekd.document.payment_term')
        payment_term_ids = payment_term_obj.search(self.payment_term.domain)
        if len(payment_term_ids) == 1:
            return payment_term_ids[0]
        return False

    def default_amount(self):
        return Transaction().context.get('amount') or Decimal('0.0')

    def default_company(self):
        return Transaction().context.get('company') or False

    def get_fields(self, ids, names):
        if not ids:
            return {}
        res = {}
        for line in self.browse(ids):
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(line.id, False)
                if name == 'number_doc':
                    if line.direct_document == 'invoice_revenue':
                        res[name][line.id] = line.number_our
                    else:
                        res[name][line.id] = line.number_in
                elif name == 'state_doc':
                    res[name][line.id] = line.state
                elif name == 'amount_doc':
                    for line_spec in line.lines:
                        if line_spec.type == 'line':
                            res[name][line.id] += line_spec.amount
                elif name == 'amount_tax':
                    for line_spec in line.lines:
                        if line_spec.type == 'line':
                            res[name][line.id] += line_spec.amount_tax
                elif name == 'from_to_party':
                    if line.direct_document == 'invoice_revenue':
                        res[name][line.id] = line.from_party.id
                    else:
                        res[name][line.id] = line.to_party.id
                elif name == 'template_invoice':
                    res[name][line.id] = line.template.id
        return res

    def set_fields(self, ids, name, value):
        if isinstance(ids, list):
            ids = ids[0]
        if not value:
            return
        document = self.browse(ids)
        if name == 'state_doc':
            self.write(ids, {
                'state': value,
            })
        elif name == 'template_invoice':
            self.write(ids, {
                'template': value,
            })
        elif name == 'number_doc':
            if document.direct_document == 'invoice_revenue':
                self.write(ids, {
                    'number_our': value,
                })
            else:
                self.write(ids, {
                    'number_in': value,
                })
        elif name == 'amount_doc':
            self.write(ids, {
                'amount': value,
            })
        elif name == 'from_to_party':
            if document.direct_document == 'invoice_revenue':
                self.write(ids, {
                    'from_party': value,
                })
            else:
                self.write(ids, {
                    'to_party': value,
                })

    def template_select_get(self):
        context = Transaction().context
        template_obj = self.pool.get('ekd.document.template')
        raise Exception(str(context))
        template_ids = template_obj.search([
            'type_account', '=',
            context.get('direct_document', 'invoice_revenue')
        ])
        res = []
        for template in template_obj.browse(template_ids):
            res.append([template.id, template.name])
        return res

    def template_search(self, name, domain=[]):
        if name == 'template_invoice':
            for table, exp, value in domain:
                return [('template', 'ilike', value)]
        elif name == 'from_party_doc':
            document_obj = self.pool.get('ekd.document')
            table, exp, value = domain[0]
            find_ids = document_obj.search([('from_party', 'ilike', value)])
            return [('document', 'in', find_ids)]
        elif name == 'to_party_doc':
            document_obj = self.pool.get('ekd.document')
            table, exp, value = domain[0]
            find_ids = document_obj.search([('to_party', 'ilike', value)])
            return [('document', 'in', find_ids)]

    def get_rec_name(self, ids, name):
        if not ids:
            return {}
        return self.pool.get('ekd.document').get_rec_name(ids, name)

    def documents_base_get(self):
        dictions_obj = self.pool.get('ir.dictions')
        res = []
        diction_ids = dictions_obj.search([
            ('model', '=', self._name),
            ('pole', '=', 'document_base'),
        ],
                                          order=[('sequence', 'ASC')])
        if diction_ids:
            for diction in dictions_obj.browse(diction_ids):
                res.append([diction.key, diction.value])
        return res

    def on_change_document_base(self, vals):
        if not vals.get('document_base'):
            return {}
        model, model_id = vals.get('document_base').split(',')
        if not model or model_id == '0':
            return {}
        model_obj = self.pool.get(model)
        model_line = model_obj.browse(int(model_id))
        lines_new = {}
        field_import = [
            'product_ref', 'type_tax', 'tax', 'type', 'currency', 'unit',
            'currency_digits', 'unit_price', 'amount_tax', 'product',
            'description', 'type_product', 'unit_digits', 'amount', 'quantity'
        ]

        if hasattr(model_obj, 'lines'):
            for line_new in model_line.lines:
                if line_new.type == 'line':
                    lines_new.setdefault('add', []).append(
                        line_new.read(line_new.id, field_import))
            return {
                'from_to_party': model_line.from_to_party.id,
                'parent': int(model_id),
                'lines': lines_new
            }
        else:
            return {
                'from_to_party': model_line.from_to_party.id,
                'parent': int(model_id)
            }

    def create(self, vals):
        later = {}
        vals = vals.copy()
        cursor = Transaction().cursor
        for field in vals:
            if field in self._columns\
                    and hasattr(self._columns[field], 'set'):
                later[field] = vals[field]
        for field in later:
            del vals[field]
        if cursor.nextid(self._table):
            cursor.setnextid(self._table, cursor.currid(self._table))
        new_id = super(DocumentInvoice, self).create(vals)
        invoice = self.browse(new_id)
        new_id = invoice.document.id
        cr = Transaction().cursor
        cr.execute('UPDATE "' + self._table + '" SET id = %s '\
                        'WHERE id = %s', (invoice.document.id, invoice.id))
        ModelStorage.delete(self, invoice.id)
        self.write(new_id, later)
        res = self.browse(new_id)
        return res.id

    def delete(self, ids):
        cr = Transaction().cursor
        doc_lines_obj = self.pool.get('ekd.document.line.product')
        for document in self.browse(ids):
            if document.state == 'deleted' and document.deleting:
                return super(DocumentInvoice, self).delete(ids)
            else:
                doc_lines_obj.write([x.id for x in document.lines], {
                    'state': 'deleted',
                    'deleting': True
                })
                self.write(document.id, {'state': 'deleted', 'deleting': True})
        return True

    def button_add_number(self, ids):
        return self.new_number(ids)

    def button_post(self, ids):
        return self.post(ids)

    def button_obtained(self, ids):
        return self.obtained(ids)

    def button_delivered(self, ids):
        return self.delivered(ids)

    def button_paid(self, ids):
        return self.paid(ids)

    def button_part_paid(self, ids):
        return self.part_paid(ids)

    def button_to_pay(self, ids):
        return self.to_pay(ids)

    def button_send(self, ids):
        return self.send(ids)

    def button_print(self, ids):
        return self.print_document(ids)

    def button_cancel(self, ids):
        return self.cancel(ids)

    def button_draft(self, ids):
        return self.draft(ids)

    def button_restore(self, ids):
        return self.draft(ids)

    def new_number(self, ids):
        sequence_obj = self.pool.get('ir.sequence')
        for document in self.browse(ids):

            if document.template and document.template.sequence:
                sequence = sequence_obj.get_id(document.template.sequence.id)
            else:
                raise Exception('Error', 'Sequence for document not find!')
            self.write(document.id, {
                'number_doc': sequence,
            })

    def post(self, ids):
        return self.write(ids, {
            'state': 'posted',
        })

    def obtained(self, ids):
        return self.write(ids, {
            'state': 'obtained',
        })

    def delivered(self, ids):
        return self.write(ids, {
            'state': 'delivered',
        })

    def send(self, ids):
        return self.write(ids, {
            'state': 'sended',
        })

    def to_pay(self, ids):
        return self.write(ids, {
            'state': 'to_pay',
        })

    def paid(self, ids):
        return self.write(ids, {
            'state': 'paid',
        })

    def part_paid(self, ids):
        return self.write(ids, {
            'state': 'part_paid',
        })

    def print_document(self, ids):
        return self.write(ids, {
            'state': 'printed',
        })

    def draft(self, ids):
        cr = Transaction().cursor
        doc_lines_obj = self.pool.get('ekd.document.line.product')
        for document in self.browse(ids):
            doc_lines_obj.write([x.id for x in document.lines], {
                'state': 'draft',
                'deleting': False
            })
            self.write(document.id, {'state': 'draft', 'deleting': False})

    def cancel(self, ids):
        return self.write(ids, {
            'state': 'canceled',
        })
Example #24
0
class UPSConfiguration(ModelSingleton, ModelSQL, ModelView):
    """
    Configuration settings for UPS.
    """
    __name__ = 'ups.configuration'

    license_key = fields.Char('UPS License Key', required=True)
    user_id = fields.Char('UPS User Id', required=True)
    password = fields.Char('UPS User Password', required=True)
    shipper_no = fields.Char('UPS Shipper Number', required=True)
    is_test = fields.Boolean('Is Test')
    negotiated_rates = fields.Boolean('Use negotiated rates')
    uom_system = fields.Selection([
        ('00', 'Metric Units Of Measurement'),
        ('01', 'English Units Of Measurement'),
    ], 'UOM System', required=True)
    weight_uom = fields.Function(
        fields.Many2One('product.uom', 'Weight UOM'),
        'get_default_uom'
    )
    weight_uom_code = fields.Function(
        fields.Char('Weight UOM code'), 'get_uom_code'
    )
    length_uom = fields.Function(
        fields.Many2One('product.uom', 'Length UOM'),
        'get_default_uom'
    )

    @staticmethod
    def default_uom_system():
        return '01'

    @property
    def logger(self):
        """
        Returns logger instance for UPS
        """
        return Logger('trytond_ups')

    def get_default_uom(self, name):
        """
        Return default UOM on basis of uom_system
        """
        UOM = Pool().get('product.uom')

        uom_map = {
            '00': {  # Metric
                'weight': 'kg',
                'length': 'cm',
            },
            '01': {  # English
                'weight': 'lb',
                'length': 'in',
            }
        }

        return UOM.search([
            ('symbol', '=', uom_map[self.uom_system][name[:-4]])
        ])[0].id

    def get_uom_code(self, name):
        """
        Return UOM code names depending on the system
        """
        uom_map = {
            '00': {  # Metric
                'weight_uom_code': 'KGS',
                'length_uom_code': 'cm',
            },
            '01': {  # English
                'weight_uom_code': 'LBS',
                'length_uom_code': 'in',
            }
        }

        return uom_map[self.uom_system][name]

    @classmethod
    def __setup__(cls):
        super(UPSConfiguration, cls).__setup__()
        cls._error_messages.update({
            'ups_credentials_required':
                'UPS settings on UPS configuration are incomplete.',
        })

    def api_instance(self, call='confirm', return_xml=False):
        """Return Instance of UPS
        """
        if not all([
            self.license_key,
            self.user_id,
            self.password,
            self.uom_system,
        ]):
            self.raise_user_error('ups_credentials_required')

        if call == 'confirm':
            call_method = ShipmentConfirm
        elif call == 'accept':
            call_method = ShipmentAccept
        elif call == 'void':
            call_method = ShipmentVoid
        elif call == 'rate':
            call_method = RatingService
        elif call == 'address_val':
            call_method = AddressValidation
        else:
            call_method = None

        if call_method:
            return call_method(
                license_no=self.license_key,
                user_id=self.user_id,
                password=self.password,
                sandbox=self.is_test,
                return_xml=return_xml
            )
class BalancePartyPeriod(ModelSQL, ModelView):
    "Turnover and Balances parties (Period)"
    _name = "ekd.balances.party.period"
    _description =__doc__
    _order_name = 'period.end_date'

    def get_balance_end(self, ids, names):
        if not ids:
            return {}
        res={}
        for balance in self.browse(ids):
            type_balance = balance.account.type_balance
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(balance.id, Decimal('0.0'))
                amount = balance.balance_dt-balance.balance_ct+balance.debit-balance.credit
                if name == 'balance_end':
                    res[name][balance.id] = amount
                elif name == 'balance':
                    res[name][balance.id] = balance.balance_dt-balance.balance_ct
                elif name == 'balance_dt_end':
                    if type_balance == 'active' or (type_balance == 'both' and amount > 0):
                        res[name][balance.id] = amount
                elif name == 'balance_ct_end':
                    if type_balance == 'passive' or (type_balance == 'both' and amount < 0):
                        res[name][balance.id] = -amount

        return res

    account = fields.Many2One('ekd.balances.party', 'Analytic Account', required=True,  select=2, ondelete="CASCADE")
    period = fields.Many2One('ekd.period', 'Period', select=2, 
                    domain=[
                        ('company','=',Eval('company'))
                    ],)
    balance_dt = fields.Numeric('Debit Start', digits=(16, Eval('currency_digits', 2)))
    balance_ct = fields.Numeric('Credit Start', digits=(16, Eval('currency_digits', 2)))
    debit = fields.Numeric('Debit Turnover', digits=(16, Eval('currency_digits', 2)))
    credit = fields.Numeric('Credit Turnover', digits=(16, Eval('currency_digits', 2)))
    balance_end_dt = fields.Numeric('Debit End', digits=(16, Eval('currency_digits', 2)))
    balance_end_ct = fields.Numeric('Credit End', digits=(16, Eval('currency_digits', 2)))
    balance_dt_end = fields.Function(fields.Numeric('Debit End', 
                digits=(16, Eval('currency_digits', 2))), 'get_balance_end')
    balance_ct_end = fields.Function(fields.Numeric('Credit End', 
                digits=(16, Eval('currency_digits', 2))), 'get_balance_end')
    currency_digits = fields.Function(fields.Integer('Currency Digits'), 'currency_digits_get')
    dt_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 
                'Ref entry debit lines'), 'get_entry')
    ct_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 
                'Ref entry credit lines'), 'get_entry')
    state = fields.Selection([
                ('draft','Draft'),
                ('open','Open'),
                ('done','Closed'),
                ('deleted','Deleted')
                ], 'State', required=True)
    parent = fields.Many2One('ekd.balances.party.period','ID Parent balance')
    transfer = fields.Many2One('ekd.balances.party.period','ID Transfer balance')
    deleted = fields.Boolean('Flag Deleting')
    active = fields.Boolean('Active')

    def __init__(self):
        super(BalancePartyPeriod, self).__init__()

        self._order.insert(0, ('period', 'DESC'))

        self._sql_constraints += [
                 ('balance_party_uniq', 'UNIQUE(account,period)',\
              'period, account - must be unique per balance!'),
                          ]

    def init(self, module_name):
        cursor = Transaction().cursor
        super(BalancePartyPeriod, self).init(module_name)
        table = TableHandler(cursor, self, module_name)
        # Проверяем счетчик
        cursor.execute("SELECT last_value, increment_by FROM %s"%table.sequence_name)
        last_value, increment_by = cursor.fetchall()[0]

        # Устанавливаем счетчик
        if str(last_value)[len(str(last_value))-1] != str(_ID_TABLES_BALANCES_PERIOD[self._table]):
            cursor.execute("SELECT setval('"+table.sequence_name+"', %s, true)"%_ID_TABLES_BALANCES_PERIOD[self._table])
        if increment_by != 10:
            cursor.execute("ALTER SEQUENCE "+table.sequence_name+" INCREMENT 10")

    def default_currency_digits(self):
        return 2

    def default_active(self):
        return True

    def default_state(self):
        return 'draft'

    def currency_digits_get(self, ids, name):
        res = {}.fromkeys(ids, 2)
        for line in self.browse(ids):
            if line.account.account.currency:
                res[line.id] = line.account.account.currency.digits or 2
        return res

    def get_entry(self, ids, name):
        move_line = self.pool.get('ekd.account.move.line')
        move_line_analytic_dt = self.pool.get('ekd.account.move.line.analytic_dt')
        move_line_analytic_ct = self.pool.get('ekd.account.move.line.analytic_ct')
        res = {}
        for balance in self.browse(ids):
            if name == 'dt_line':
                line_analytic_dt = move_line_analytic_dt.search([('ref_period','=', balance.id)])
                res[balance.id] = move_line_analytic_dt.read(line_analytic_ct, ['move_line'])
            elif name == 'ct_line':
                line_analytic_ct = move_line_analytic_ct.search([('ref_period','=', balance.id)])
                res[balance.id] = move_line_analytic_ct.read(line_analytic_ct, ['move_line'])
        return res

    def set_entries_field(self, id, name, value):
        assert name in ('dt_line', 'ct_line', 'lines'), 'Invalid name'
        return

    def search_domain(self, domain, active_test=True):
        domain_new=[]
        if domain[0] == 'AND':
            for (left_val, center_val, rigth_val) in domain[1]:
                if left_val == 'period.start_date':
                    rigth_val = datetime.datetime.now()
                elif left_val == 'period.end_date':
                    rigth_val = datetime.datetime.now()
                domain_new.append((left_val, center_val, rigth_val))
        else:
            for (left_val, center_val, rigth_val) in domain:
                if left_val == 'period.start_date':
                    rigth_val = datetime.datetime.now()
                elif left_val == 'period.end_date':
                    rigth_val = datetime.datetime.now()
                domain_new.append((left_val, center_val, rigth_val))
        return super(BalancePartyPeriod, self).search_domain(domain_new, active_test=active_test)

    # Процедура переноса остатков (если есть более поздние)
    def transfer_balance(self, transfer_id, vals):
        balance = self.browse(transfer_id)
        self.write(transfer_id, {
                        'balance_dt': vals.get('balance_dt'),
                        'balance_ct': vals.get('balance_ct'),
                        })
        if balance.transfer and vals.get('transfer', True):
            self.transfer_balance(balance.transfer.id, {
                        'balance_dt':balance.balance_dt_end,
                        'balance_ct':balance.balance_ct_end,
                        'transfer': vals.get('transfer', True),
                        })

    def transfer_balances(self, vals=None):
        '''
            Transfer Balances of account - Перенос остатков.
            period     - словарь идентификаторов периодов (периоды уже отсортированны!!!)
        '''
        if vals is None and not vals.get('company', False) and not vals.get('account', False):
            return False

        balance_ids= {}
        for period in vals.get('periods'):
            if not balance_ids:
                balance_ids = self.search([
                                ('period','=',period),
                                ('account','=', vals.get('account')),
                                ])
                continue
            for balance_id in balance_ids:
                balance_line = self.browse(balance_id)
                if balance_line.balance_dt_end or balance_line.balance_ct_end:
                    if balance_line.transfer:
                        self.transfer_balance(balance_line.transfer.id, {
                                    'balance_dt': balance_line.balance_dt_end,
                                    'balance_ct': balance_line.balance_ct_end,
                                    })
                    else:
                        balance_new_id = self.search([
                            ('period','=',period),
                            ('account','=',vals.get('account')),
                            ('party','=',balance_line.party.id),
                            ('model_ref','=',balance_line.model_ref),
                            ])
                        if balance_new_id:
                            self.write(balance_line.id, {
                                    'transfer': balance_new_id,
                                    })
                            self.write(balance_new_id, {
                                    'balance_dt': balance_line.balance_dt_end,
                                    'balance_ct': balance_line.balance_ct_end,
                                    })

                        else:
                            self.write(balance_line.id, {
                                    'transfer': self.create({
                                            'company': vals.get('company'),
                                            'period': period,
                                            'account':  balance_line.account.id ,
                                            'party': balance_line.party.id,
                                            'model_ref': balance_line.model_ref,
                                            'balance_dt': balance_line.balance_dt_end,
                                            'balance_ct': balance_line.balance_ct_end,
                                            })
                                    })

            balance_ids = self.search([
                                ('period','=',period),
                                ('account','=', vals.get('account')),
                                ])

        return True
Example #26
0
class ProductSaleChannelListing:
    "Product Sale Channel"
    __name__ = 'product.product.channel_listing'

    price_tiers = fields.One2Many(
        'product.price_tier', 'product_listing', 'Price Tiers'
    )
    magento_product_type = fields.Selection(
        [
            (None, ''),
            ('simple', 'Simple'),
            ('configurable', 'Configurable'),
            ('grouped', 'Grouped'),
            ('bundle', 'Bundle'),
            ('virtual', 'Virtual'),
            ('downloadable', 'Downloadable'),
        ], 'Magento Product Type', readonly=True, states={
            "invisible": Eval('channel_source') != 'magento'
        }, depends=['channel_source']
    )

    @classmethod
    def __setup__(cls):
        super(ProductSaleChannelListing, cls).__setup__()
        cls._error_messages.update({
            'multi_inventory_update_fail':
            "FaultCode: %s, FaultMessage: %s",
        })

    @classmethod
    def create_from(cls, channel, product_data):
        """
        Create a listing for the product from channel and data
        """
        Product = Pool().get('product.product')

        if channel.source != 'magento':
            return super(ProductSaleChannelListing, cls).create_from(
                channel, product_data
            )

        try:
            product, = Product.search([
                ('code', '=', product_data['sku']),
            ])
        except ValueError:
            cls.raise_user_error("No product found for mapping")

        listing = cls(
            channel=channel,
            product=product,
            # Do not match with SKU. Magento f***s up when there are
            # numeric SKUs
            product_identifier=product_data['product_id'],
            magento_product_type=product_data['type'],
        )
        listing.save()
        return listing

    def export_inventory(self):
        """
        Export inventory of this listing
        """
        if self.channel.source != 'magento':
            return super(ProductSaleChannelListing, self).export_inventory()

        return self.export_bulk_inventory([self])

    @classmethod
    def export_bulk_inventory(cls, listings):
        """
        Bulk export inventory to magento.

        Do not rely on the return value from this method.
        """
        SaleChannelListing = Pool().get('product.product.channel_listing')

        if not listings:
            # Nothing to update
            return

        non_magento_listings = cls.search([
            ('id', 'in', map(int, listings)),
            ('channel.source', '!=', 'magento'),
        ])
        if non_magento_listings:
            super(ProductSaleChannelListing, cls).export_bulk_inventory(
                non_magento_listings
            )
        magento_listings = filter(
            lambda l: l not in non_magento_listings, listings
        )

        log.info(
            "Fetching inventory of %d magento listings"
            % len(magento_listings)
        )

        inventory_channel_map = defaultdict(list)
        for listing in magento_listings:
            channel = listing.channel

            product_data = {
                'qty': listing.quantity,
            }

            # TODO: Get this from availability used
            if listing.magento_product_type == 'simple':
                # Only send inventory for simple products
                product_data['is_in_stock'] = '1' \
                    if listing.quantity > 0 else '0'
            else:
                # configurable, bundle and everything else
                product_data['is_in_stock'] = '1'

            # group inventory xml by channel
            inventory_channel_map[channel].append([
                listing.product_identifier, product_data
            ])

        for channel, product_data_list in inventory_channel_map.iteritems():
            with magento.Inventory(
                    channel.magento_url,
                    channel.magento_api_user,
                    channel.magento_api_key) as inventory_api:
                for product_data_batch in batch(product_data_list, 50):
                    log.info(
                        "Pushing inventory of %d products to magento"
                        % len(product_data_batch)
                    )
                    response = inventory_api.update_multi(product_data_batch)
                    # Magento bulk API will not raise Faults.
                    # Instead the response contains the faults as a dict
                    for i, result in enumerate(response):
                        if result is not True:
                            if result.get('isFault') is True and \
                                    result['faultCode'] == '101':
                                listing, = SaleChannelListing.search([
                                    ('product_identifier', '=', product_data_batch[i][0]),  # noqa
                                    ('channel', '=', channel.id),
                                ])
                                listing.state = 'disabled'
                                listing.save()
                            else:
                                cls.raise_user_error(
                                    'multi_inventory_update_fail',
                                    (result['faultCode'], result['faultMessage'])  # noqa
                                )
Example #27
0
class Account(DeactivableMixin, tree('distribution_parents'), tree(), ModelSQL,
              ModelView):
    'Analytic Account'
    __name__ = 'analytic_account.account'
    name = fields.Char('Name', required=True, translate=True, select=True)
    code = fields.Char('Code', select=True)
    company = fields.Many2One('company.company', 'Company', required=True)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')
    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',
                           })
    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',
                             })
    childs = fields.One2Many('analytic_account.account',
                             'parent',
                             'Children',
                             states={
                                 'invisible': Eval('id', -1) < 0,
                             },
                             domain=[
                                 ('company', '=', Eval('company', -1)),
                             ])
    balance = fields.Function(
        Monetary("Balance", currency='currency', digits='currency'),
        'get_balance')
    credit = fields.Function(
        Monetary("Credit", currency='currency', digits='currency'),
        'get_credit_debit')
    debit = fields.Function(
        Monetary("Debit", currency='currency', digits='currency'),
        'get_credit_debit')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('opened', 'Opened'),
        ('closed', 'Closed'),
    ],
                             "State",
                             required=True,
                             sort=False)
    note = fields.Text('Note')
    distributions = fields.One2Many('analytic_account.account.distribution',
                                    'parent',
                                    "Distributions",
                                    states={
                                        'invisible':
                                        Eval('type') != 'distribution',
                                        'required':
                                        Eval('type') == 'distribution',
                                    })
    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'))

    @classmethod
    def __register__(cls, module_name):
        super(Account, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

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

        # Migration from 5.0: remove display_balance
        table.drop_column('display_balance')

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

    @staticmethod
    def default_type():
        return 'normal'

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def validate_fields(cls, accounts, field_names):
        super().validate_fields(accounts, field_names)
        cls.check_distribution(accounts, field_names)

    @classmethod
    def check_distribution(cls, accounts, field_names=None):
        if field_names and not (field_names & {'distributions', 'type'}):
            return
        for account in accounts:
            if account.type != 'distribution':
                return
            if sum((d.ratio for d in account.distributions)) != 1:
                raise AccountValidationError(
                    gettext('analytic_account.msg_invalid_distribution',
                            account=account.rec_name))

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

    @fields.depends('parent', 'type', '_parent_parent.id',
                    '_parent_parent.root', '_parent_parent.type')
    def on_change_parent(self):
        if (self.parent and self.parent.id is not None and self.parent.id > 0
                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 = list({}.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.credit, 0) - Coalesce(line.debit, 0)),
                   where=(table.type != 'view')
                   & table.id.in_(all_ids)
                   & (table.active == Literal(True)) & line_query,
                   group_by=table.id))
        account_sum = defaultdict(Decimal)
        for account_id, value in cursor:
            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]
            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 == Literal(True))
                                       & line_query,
                                       group_by=table.id))

        for row in cursor:
            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 + ' - ' + str(self.name)
        else:
            return str(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'
        code_value = clause[2]
        if clause[1].endswith('like'):
            code_value = lstrip_wildcard(clause[2])
        return [
            bool_op,
            ('code', clause[1], code_value) + tuple(clause[3:]),
            (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, current_amount = result[i]
                    rounding = self.currency.rounding.copy_sign(remainder)
                    result[i] = (account, current_amount + rounding)
                    remainder -= rounding
                    i = (i + 1) % len(result)
            assert sum(a for _, a in result) == amount
            return result
Example #28
0
class UIMenu(sequence_ordered(), ModelSQL, ModelView):
    "UI menu"
    __name__ = 'ir.ui.menu'

    name = fields.Char('Menu', required=True, translate=True)
    childs = fields.One2Many('ir.ui.menu', 'parent', 'Children')
    parent = fields.Many2One('ir.ui.menu', 'Parent Menu', select=True,
            ondelete='CASCADE')
    groups = fields.Many2Many('ir.ui.menu-res.group',
       'menu', 'group', 'Groups')
    complete_name = fields.Function(fields.Char('Complete Name'),
        'get_rec_name', searcher='search_rec_name')
    icon = fields.Selection('list_icons', 'Icon', translate=False)
    action = fields.Function(fields.Reference('Action',
            selection=[
                ('', ''),
                ('ir.action.report', 'ir.action.report'),
                ('ir.action.act_window', 'ir.action.act_window'),
                ('ir.action.wizard', 'ir.action.wizard'),
                ('ir.action.url', 'ir.action.url'),
                ]), 'get_action', setter='set_action')
    action_keywords = fields.One2Many('ir.action.keyword', 'model',
        'Action Keywords')
    active = fields.Boolean('Active')
    favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')

    @classmethod
    def __setup__(cls):
        super(UIMenu, cls).__setup__()
        cls._error_messages.update({
                'wrong_name': ('"%%s" is not a valid menu name because it is '
                    'not allowed to contain "%s".' % SEPARATOR),
                })

    @classmethod
    def order_complete_name(cls, tables):
        return cls.name.convert_order('name', tables, cls)

    @staticmethod
    def default_icon():
        return 'tryton-open'

    @staticmethod
    def default_sequence():
        return 10

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def list_icons():
        pool = Pool()
        Icon = pool.get('ir.ui.icon')
        return sorted(CLIENT_ICONS
            + [(name, name) for _, name in Icon.list_icons()])

    @classmethod
    def validate(cls, menus):
        super(UIMenu, cls).validate(menus)
        cls.check_recursion(menus)
        for menu in menus:
            menu.check_name()

    def check_name(self):
        if SEPARATOR in self.name:
            self.raise_user_error('wrong_name', (self.name,))

    def get_rec_name(self, name):
        parent = self.parent
        name = self.name
        while parent:
            name = parent.name + SEPARATOR + name
            parent = parent.parent
        return name

    @classmethod
    def search_rec_name(cls, name, clause):
        if isinstance(clause[2], basestring):
            values = clause[2].split(SEPARATOR.strip())
            values.reverse()
            domain = []
            field = 'name'
            for name in values:
                domain.append((field, clause[1], name.strip()))
                field = 'parent.' + field
        else:
            domain = [('name',) + tuple(clause[1:])]
        ids = [m.id for m in cls.search(domain, order=[])]
        return [('parent', 'child_of', ids)]

    @classmethod
    def search_global(cls, text):
        # TODO improve search clause
        for record in cls.search([
                    ('rec_name', 'ilike', '%%%s%%' % text),
                    ]):
            if record.action:
                yield record, record.rec_name, record.icon

    @classmethod
    def search(cls, domain, offset=0, limit=None, order=None, count=False,
            query=False):
        menus = super(UIMenu, cls).search(domain, offset=offset, limit=limit,
                order=order, count=False, query=query)
        if query:
            return menus

        if menus:
            parent_ids = {x.parent.id for x in menus if x.parent}
            parents = set()
            for sub_parent_ids in grouped_slice(parent_ids):
                parents.update(cls.search([
                            ('id', 'in', list(sub_parent_ids)),
                            ]))
            menus = [x for x in menus
                if (x.parent and x.parent in parents) or not x.parent]

        if count:
            return len(menus)
        return menus

    @classmethod
    def get_action(cls, menus, name):
        pool = Pool()
        actions = dict((m.id, None) for m in menus)
        with Transaction().set_context(active_test=False):
            menus = cls.browse(menus)
        action_keywords = sum((list(m.action_keywords) for m in menus), [])
        key = lambda k: k.action.type
        action_keywords.sort(key=key)
        for type, action_keywords in groupby(action_keywords, key=key):
            action_keywords = list(action_keywords)
            for action_keyword in action_keywords:
                model = action_keyword.model
                actions[model.id] = '%s,-1' % type

            Action = pool.get(type)
            action2keyword = {k.action.id: k for k in action_keywords}
            with Transaction().set_context(active_test=False):
                factions = Action.search([
                        ('action', 'in', action2keyword.keys()),
                        ])
            for action in factions:
                model = action2keyword[action.id].model
                actions[model.id] = str(action)
        return actions

    @classmethod
    def set_action(cls, menus, name, value):
        pool = Pool()
        ActionKeyword = pool.get('ir.action.keyword')
        action_keywords = []
        transaction = Transaction()
        for i in range(0, len(menus), transaction.database.IN_MAX):
            sub_menus = menus[i:i + transaction.database.IN_MAX]
            action_keywords += ActionKeyword.search([
                ('keyword', '=', 'tree_open'),
                ('model', 'in', [str(menu) for menu in sub_menus]),
                ])
        if action_keywords:
            with Transaction().set_context(_timestamp=False):
                ActionKeyword.delete(action_keywords)
        if not value:
            return
        if isinstance(value, basestring):
            action_type, action_id = value.split(',')
        else:
            action_type, action_id = value
        if int(action_id) <= 0:
            return
        Action = pool.get(action_type)
        action = Action(int(action_id))
        to_create = []
        for menu in menus:
            with Transaction().set_context(_timestamp=False):
                to_create.append({
                        'keyword': 'tree_open',
                        'model': str(menu),
                        'action': action.action.id,
                        })
        if to_create:
            ActionKeyword.create(to_create)

    @classmethod
    def get_favorite(cls, menus, name):
        pool = Pool()
        Favorite = pool.get('ir.ui.menu.favorite')
        user = Transaction().user
        favorites = Favorite.search([
                ('menu', 'in', [m.id for m in menus]),
                ('user', '=', user),
                ])
        menu2favorite = dict((m.id, False if m.action else None)
            for m in menus)
        menu2favorite.update(dict((f.menu.id, True) for f in favorites))
        return menu2favorite
Example #29
0
class ApacheII(ModelSQL, ModelView):
    'Apache II scoring'
    __name__ = 'gnuhealth.icu.apache2'

    name = fields.Many2One('gnuhealth.inpatient.registration',
                           'Registration Code',
                           required=True)
    score_date = fields.DateTime('Date',
                                 help="Date of the score",
                                 required=True)

    age = fields.Integer('Age', help='Patient age in years')
    temperature = fields.Float('Temperature', help='Rectal temperature')
    mean_ap = fields.Integer('MAP', help='Mean Arterial Pressure')
    heart_rate = fields.Integer('Heart Rate')
    respiratory_rate = fields.Integer('Respiratory Rate')
    fio2 = fields.Float('FiO2')
    pao2 = fields.Integer('PaO2')
    paco2 = fields.Integer('PaCO2')
    aado2 = fields.Integer('A-a DO2', on_change_with=['fio2', 'pao2', 'paco2'])

    ph = fields.Float('pH')
    serum_sodium = fields.Integer('Sodium')
    serum_potassium = fields.Float('Potassium')
    serum_creatinine = fields.Float('Creatinine')
    arf = fields.Boolean('ARF', help='Acute Renal Failure')
    wbc = fields.Float('WBC',
                       help="White blood cells x 1000 - if you"
                       " want to input 4500 wbc / ml, type in 4.5")
    hematocrit = fields.Float('Hematocrit')
    gcs = fields.Integer(
        'GSC',
        help='Last Glasgow Coma Scale'
        ' You can use the GSC calculator from the Patient Evaluation Form.')
    chronic_condition = fields.Boolean(
        'Chronic condition', help='Organ Failure or immunocompromised patient')
    hospital_admission_type = fields.Selection(
        [(None, ''), ('me', 'Medical or emergency postoperative'),
         ('el', 'elective postoperative')],
        'Hospital Admission Type',
        states={
            'invisible': Not(Bool(Eval('chronic_condition'))),
            'required': Bool(Eval('chronic_condition'))
        },
        sort=False)

    apache_score = fields.Integer(
        'Score',
        on_change_with=[
            'age', 'temperature', 'mean_ap', 'heart_rate', 'respiratory_rate',
            'fio2', 'pao2', 'aado2', 'ph', 'serum_sodium', 'serum_potassium',
            'serum_creatinine', 'arf', 'wbc', 'hematocrit', 'gcs',
            'chronic_condition', 'hospital_admission_type'
        ])

    #Default FiO2 PaO2 and PaCO2 so we do the A-a gradient
    #calculation with non-null values

    def on_change_with_aado2(self):
        # Calculates the Alveolar-arterial difference
        # based on FiO2, PaCO2 and PaO2 values
        if (self.fio2 and self.paco2 and self.pao2):
            return (713 * self.fio2) - (self.paco2 / 0.8) - self.pao2

    def on_change_with_apache_score(self):
        # Calculate the APACHE SCORE from the variables in the

        total = 0
        # Age
        if (self.age):
            if (self.age > 44 and self.age < 55):
                total = total + 2
            elif (self.age > 54 and self.age < 65):
                total = total + 3
            elif (self.age > 64 and self.age < 75):
                total = total + 5
            elif (self.age > 74):
                total = total + 6

        # Temperature
        if (self.temperature):
            if ((self.temperature >= 38.5 and self.temperature < 39)
                    or (self.temperature >= 34 and self.temperature < 36)):
                total = total + 1
            elif (self.temperature >= 32 and self.temperature < 34):
                total = total + 2
            elif ((self.temperature >= 30 and self.temperature < 32)
                  or (self.temperature >= 39 and self.temperature < 41)):
                total = total + 3
            elif (self.temperature >= 41 or self.temperature < 30):
                total = total + 4

        # Mean Arterial Pressure (MAP)
        if (self.mean_ap):
            if ((self.mean_ap >= 110 and self.mean_ap < 130)
                    or (self.mean_ap >= 50 and self.mean_ap < 70)):
                total = total + 2
            elif (self.mean_ap >= 130 and self.mean_ap < 160):
                total = total + 3
            elif (self.mean_ap >= 160 or self.mean_ap < 50):
                total = total + 4

        # Heart Rate
        if (self.heart_rate):
            if ((self.heart_rate >= 55 and self.heart_rate < 70)
                    or (self.heart_rate >= 110 and self.heart_rate < 140)):
                total = total + 2
            elif ((self.heart_rate >= 40 and self.heart_rate < 55)
                  or (self.heart_rate >= 140 and self.heart_rate < 180)):
                total = total + 3
            elif (self.heart_rate >= 180 or self.heart_rate < 40):
                total = total + 4

        # Respiratory Rate
        if (self.respiratory_rate):
            if ((self.respiratory_rate >= 10 and self.respiratory_rate < 12) or
                (self.respiratory_rate >= 25 and self.respiratory_rate < 35)):
                total = total + 1
            elif (self.respiratory_rate >= 6 and self.respiratory_rate < 10):
                total = total + 2
            elif (self.respiratory_rate >= 35 and self.respiratory_rate < 50):
                total = total + 3
            elif (self.respiratory_rate >= 50 or self.respiratory_rate < 6):
                total = total + 4

        # FIO2
        if (self.fio2):
            # If Fi02 is greater than 0.5, we measure the AaDO2 gradient
            # Otherwise, we take into account the Pa02 value

            if (self.fio2 >= 0.5):
                if (self.aado2 >= 200 and self.aado2 < 350):
                    total = total + 2

                elif (self.aado2 >= 350 and self.aado2 < 500):
                    total = total + 3

                elif (self.aado2 >= 500):
                    total = total + 4

            else:
                if (self.pao2 >= 61 and self.pao2 < 71):
                    total = total + 1

                elif (self.pao2 >= 55 and self.pao2 < 61):
                    total = total + 3

                elif (self.pao2 < 55):
                    total = total + 4

        # Arterial pH
        if (self.ph):
            if (self.ph >= 7.5 and self.ph < 7.6):
                total = total + 1
            elif (self.ph >= 7.25 and self.ph < 7.33):
                total = total + 2
            elif ((self.ph >= 7.15 and self.ph < 7.25)
                  or (self.ph >= 7.6 and self.ph < 7.7)):
                total = total + 3
            elif (self.ph >= 7.7 or self.ph < 7.15):
                total = total + 4

        # Serum Sodium
        if (self.serum_sodium):
            if (self.serum_sodium >= 150 and self.serum_sodium < 155):
                total = total + 1
            elif ((self.serum_sodium >= 155 and self.serum_sodium < 160)
                  or (self.serum_sodium >= 120 and self.serum_sodium < 130)):
                total = total + 2
            elif ((self.serum_sodium >= 160 and self.serum_sodium < 180)
                  or (self.serum_sodium >= 111 and self.serum_sodium < 120)):
                total = total + 3
            elif (self.serum_sodium >= 180 or self.serum_sodium < 111):
                total = total + 4

        # Serum Potassium
        if (self.serum_potassium):
            if ((self.serum_potassium >= 3 and self.serum_potassium < 3.5) or
                (self.serum_potassium >= 5.5 and self.serum_potassium < 6)):
                total = total + 1
            elif (self.serum_potassium >= 2.5 and self.serum_potassium < 3):
                total = total + 2
            elif (self.serum_potassium >= 6 and self.serum_potassium < 7):
                total = total + 3
            elif (self.serum_potassium >= 7 or self.serum_potassium < 2.5):
                total = total + 4

        # Serum Creatinine
        if (self.serum_creatinine):
            arf_factor = 1
            if (self.arf):
                # We multiply by 2 the score if there is concomitant ARF
                arf_factor = 2
            if ((self.serum_creatinine < 0.6) or
                (self.serum_creatinine >= 1.5 and self.serum_creatinine < 2)):
                total = total + 2 * arf_factor
            elif (self.serum_creatinine >= 2 and self.serum_creatinine < 3.5):
                total = total + 3 * arf_factor
            elif (self.serum_creatinine >= 3.5):
                total = total + 4 * arf_factor

        # Hematocrit
        if (self.hematocrit):
            if (self.hematocrit >= 46 and self.hematocrit < 50):
                total = total + 1
            elif ((self.hematocrit >= 50 and self.hematocrit < 60)
                  or (self.hematocrit >= 20 and self.hematocrit < 30)):
                total = total + 2
            elif (self.hematocrit >= 60 or self.hematocrit < 20):
                total = total + 4

        # WBC ( x 1000 )
        if (self.wbc):
            if (self.wbc >= 15 and self.wbc < 20):
                total = total + 1
            elif ((self.wbc >= 20 and self.wbc < 40)
                  or (self.wbc >= 1 and self.wbc < 3)):
                total = total + 2
            elif (self.wbc >= 40 or self.wbc < 1):
                total = total + 4

        # Immnunocompromised or severe organ failure
        if (self.chronic_condition):
            if (self.hospital_admission_type == 'me'):
                total = total + 5
            else:
                total = total + 2

        return total
Example #30
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([
        ('media', 'Media'),
        ('custom_code', 'Custom Code'),
    ],
                            'Type',
                            required=True)
    file = fields.Many2One('nereid.static.file',
                           'File',
                           states={
                               'required': Equal(Eval('type'), 'media'),
                               'invisible': Not(Equal(Eval('type'), 'media'))
                           })
    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(Equal(Eval('type'), 'media'))})
    width = fields.Integer(
        'Width', states={'invisible': Not(Equal(Eval('type'), 'media'))})
    alternative_text = fields.Char(
        'Alternative Text',
        translate=True,
        states={'invisible': Not(Equal(Eval('type'), 'media'))})
    click_url = fields.Char(
        'Click URL',
        translate=True,
        states={'invisible': Not(Equal(Eval('type'), 'media'))})

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

    @classmethod
    def __register__(cls, module_name):
        super(Banner, cls).__register__(module_name)

        table = cls.__table__()
        cursor = Transaction().cursor
        # Update type from image to media
        query = table.update(columns=[table.type],
                             values=["media"],
                             where=(table.type == "image"))
        cursor.execute(*query)

    @classmethod
    def view_attributes(cls):
        return super(Banner, cls).view_attributes() + [
            ('//page[@id="image"]', 'states', {
                'invisible': Eval('type') == 'custom_code'
            }),
            ('//page[@id="custom_code"]', 'states', {
                'invisible': Eval('type') != 'custom_code'
            })
        ]

    @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')

        # XXX: Use read, as all fields are not required
        banner = self.read([self], [
            'type', 'click_url', 'file', 'file.mimetype', 'custom_code',
            'height', 'width', 'alternative_text', 'click_url'
        ])[0]

        if banner['type'] == 'media':
            if not (banner['file.mimetype']
                    and banner['file.mimetype'].startswith('image')):
                return "<!-- no html defined for mime type '%s' -->" % \
                    banner['file.mimetype']
            # 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'] == 'custom_code':
            return banner['custom_code']

    @classmethod
    def allowed_models(cls):
        MenuItem = Pool().get('nereid.cms.menuitem')

        return MenuItem.allowed_models()

    @staticmethod
    def default_type():
        return 'media'

    @staticmethod
    def default_state():
        if 'published' in Transaction().context:
            return 'published'
        return 'draft'