Exemplo n.º 1
0
    def get_move_line(self, wh_type='in', context=None):
        inventory_warehouse = self.env['warehouse'] \
            .get_warehouse_by_type('inventory')
        for inventory in self:
            cost, cost_unit = inventory.goods_id. \
                get_suggested_cost_by_warehouse(
                    inventory.warehouse_id, abs(inventory.difference_qty),
                    lot_id=inventory.new_lot_id,
                    attribute=inventory.attribute_id)

            res = {
                'type': wh_type,
                'lot': inventory.new_lot,
                'lot_id': inventory.new_lot_id.id,
                'goods_id': inventory.goods_id.id,
                'attribute_id': inventory.attribute_id.id,
                'uom_id': inventory.uom_id.id,
                'uos_id': inventory.uos_id.id,
                'cost_unit': cost_unit,
                'cost': cost,
            }

            difference_qty, difference_uos_qty = abs(inventory.difference_qty), abs(inventory.difference_uos_qty)

            # 差异数量为0,且差异辅助数量不为0时,用差异辅助数量。否则用差差异数量
            if float_is_zero(difference_qty, 2) and not float_is_zero(difference_uos_qty, 2):
                res.update({'goods_uos_qty': difference_uos_qty})
            else:
                res.update ({'goods_qty': difference_qty})

            return res
Exemplo n.º 2
0
    def product_price_update_before_done(self, forced_qty=None):
        tmpl_dict = defaultdict(lambda: 0.0)
        # adapt standard price on incomming moves if the product cost_method is 'average'
        std_price_update = {}
        for move in self.filtered(lambda move: move.location_id.usage in ('supplier', 'production') and move.product_id.cost_method == 'average'):
            product_tot_qty_available = move.product_id.qty_available + tmpl_dict[move.product_id.id]
            rounding = move.product_id.uom_id.rounding

            qty_done = 0.0
            if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
                new_std_price = move._get_price_unit()
            elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding) or \
                    float_is_zero(product_tot_qty_available + qty_done, precision_rounding=rounding):
                new_std_price = move._get_price_unit()
            else:
                # Get the standard price
                amount_unit = std_price_update.get((move.company_id.id, move.product_id.id)) or move.product_id.standard_price
                qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
                qty = forced_qty or qty_done
                new_std_price = ((amount_unit * product_tot_qty_available) + (move._get_price_unit() * qty)) / (product_tot_qty_available + qty_done)

            tmpl_dict[move.product_id.id] += qty_done
            # Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
            move.product_id.with_context(force_company=move.company_id.id).sudo().write({'standard_price': new_std_price})
            std_price_update[move.company_id.id, move.product_id.id] = new_std_price
Exemplo n.º 3
0
    def _get_difference_qty(self):
        for line in self:
            line.difference_qty = line.inventory_qty - line.real_qty
            line.difference_uos_qty = line.inventory_uos_qty - line.real_uos_qty

            if float_is_zero(line.difference_qty, 2) and not float_is_zero(line.difference_uos_qty,2):
                line.difference_qty = line.difference_uos_qty * line.goods_id.conversion
            if not float_is_zero(line.difference_qty, 2) and line.difference_uos_qty == 0:
                line.difference_uos_qty = line.difference_qty / line.goods_id.conversion
Exemplo n.º 4
0
    def test_rounding_invalid(self):
        """ verify that invalid parameters are forbidden """
        with self.assertRaises(AssertionError):
            float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_round(0.01, precision_digits=3, precision_rounding=0.01)
Exemplo n.º 5
0
 def change_prod_qty(self):
     precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
     for wizard in self:
         production = wizard.mo_id
         produced = sum(production.move_finished_ids.filtered(lambda m: m.product_id == production.product_id).mapped('quantity_done'))
         if wizard.product_qty < produced:
             raise UserError(_("You have already processed %d. Please input a quantity higher than %d ")%(produced, produced))
         production.write({'product_qty': wizard.product_qty})
         done_moves = production.move_finished_ids.filtered(lambda x: x.state == 'done' and x.product_id == production.product_id)
         qty_produced = production.product_id.uom_id._compute_quantity(sum(done_moves.mapped('product_qty')), production.product_uom_id)
         factor = production.product_uom_id._compute_quantity(production.product_qty - qty_produced, production.bom_id.product_uom_id) / production.bom_id.product_qty
         boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
         for line, line_data in lines:
             production._update_raw_move(line, line_data)
         operation_bom_qty = {}
         for bom, bom_data in boms:
             for operation in bom.routing_id.operation_ids:
                 operation_bom_qty[operation.id] = bom_data['qty']
         self._update_product_to_produce(production, production.product_qty - qty_produced)
         moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
         moves._action_assign()
         for wo in production.workorder_ids:
             operation = wo.operation_id
             if operation_bom_qty.get(operation.id):
                 cycle_number = math.ceil(operation_bom_qty[operation.id] / operation.workcenter_id.capacity)  # TODO: float_round UP
                 wo.duration_expected = (operation.workcenter_id.time_start +
                              operation.workcenter_id.time_stop +
                              cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency)
             quantity = wo.qty_production - wo.qty_produced
             if production.product_id.tracking == 'serial':
                 quantity = 1.0 if not float_is_zero(quantity, precision_digits=precision) else 0.0
             else:
                 quantity = quantity if (quantity > 0) else 0
             if float_is_zero(quantity, precision_digits=precision):
                 wo.final_lot_id = False
                 wo.active_move_line_ids.unlink()
             wo.qty_producing = quantity
             if wo.qty_produced < wo.qty_production and wo.state == 'done':
                 wo.state = 'progress'
             # assign moves; last operation receive all unassigned moves
             # TODO: following could be put in a function as it is similar as code in _workorders_create
             # TODO: only needed when creating new moves
             moves_raw = production.move_raw_ids.filtered(lambda move: move.operation_id == operation and move.state not in ('done', 'cancel'))
             if wo == production.workorder_ids[-1]:
                 moves_raw |= production.move_raw_ids.filtered(lambda move: not move.operation_id)
             moves_finished = production.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products?
             moves_raw.mapped('move_line_ids').write({'workorder_id': wo.id})
             (moves_finished + moves_raw).write({'workorder_id': wo.id})
             if quantity > 0 and wo.move_raw_ids.filtered(lambda x: x.product_id.tracking != 'none') and not wo.active_move_line_ids:
                 wo._generate_lot_ids()
     return {}
Exemplo n.º 6
0
 def create(self, values):
     line = super(SaleOrderLine, self).create(values)
     precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
     # check ordered quantity to avoid create project/task when expensing service products
     if line.state == 'sale' and not float_is_zero(line.product_uom_qty, precision_digits=precision):
         line._timesheet_service_generation()
     return line
Exemplo n.º 7
0
 def _compute_invoice_status(self):
     """
     Compute the invoice status of a SO line. Possible statuses:
     - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
       invoice. This is also hte default value if the conditions of no other status is met.
     - to invoice: we refer to the quantity to invoice of the line. Refer to method
       `_get_to_invoice_qty()` for more information on how this quantity is calculated.
     - upselling: this is possible only for a product invoiced on ordered quantities for which
       we delivered more than expected. The could arise if, for example, a project took more
       time than expected but we decided not to invoice the extra cost to the client. This
       occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity
       is removed from the list.
     - invoiced: the quantity invoiced is larger or equal to the quantity ordered.
     """
     precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
     for line in self:
         if line.state not in ('sale', 'done'):
             line.invoice_status = 'no'
         elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
             line.invoice_status = 'to invoice'
         elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\
                 float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1:
             line.invoice_status = 'upselling'
         elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0:
             line.invoice_status = 'invoiced'
         else:
             line.invoice_status = 'no'
Exemplo n.º 8
0
    def _check_rule_propositions(self, statement_line, candidates):
        ''' Check restrictions that can't be handled for each move.line separately.
        /!\ Only used by models having a type equals to 'invoice_matching'.
        :param statement_line:  An account.bank.statement.line record.
        :param candidates:      Fetched account.move.lines from query (dict).
        :return:                True if the reconciliation propositions are accepted. False otherwise.
        '''
        if not self.match_total_amount:
            return True

        # Match total residual amount.
        total_residual = sum(
            aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in
            candidates)
        line_residual = statement_line.currency_id and statement_line.amount_currency or statement_line.amount
        line_currency = statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.company_id.currency_id

        # Statement line amount is equal to the total residual.
        if float_is_zero(total_residual - line_residual, precision_rounding=line_currency.rounding):
            return True

        if line_residual > total_residual:
            amount_percentage = (total_residual / line_residual) * 100
        else:
            amount_percentage = (line_residual / total_residual) * 100
        return amount_percentage >= self.match_total_amount_param
Exemplo n.º 9
0
    def create_from_ui(self, orders):
        """在会话中结账后生成pos order,并由pos order生成相应的发货单/退货单及收款单 """
        order_ids = []
        for order in orders:
            order_data = order.get('data')
            pos_order_data = self.data_handling(order_data)
            pos_order = self.create(pos_order_data)
            order_ids.append(pos_order.id)

            prec_amt = self.env['decimal.precision'].precision_get('Amount')
            for payments in order_data.get('statement_ids'):
                if not float_is_zero(payments[2].get('amount'), precision_digits=prec_amt):
                    pos_order.add_payment(self._payment_fields(payments[2]))
            try:
                pos_order.action_pos_order_paid()
            except psycopg2.OperationalError:
                # do not hide transactional errors, the order(s) won't be saved!
                raise
            except Exception as e:
                _logger.error(u'不能完整地处理POS 订单: %s', tools.ustr(e))

            # 生成sell_delivery,并审核
            records = pos_order.create_sell_delivery()
            invoice_ids = [record.invoice_id for record in records]
            # 生成收款单,并审核
            pos_order.create_money_order(invoice_ids, pos_order.payment_line_ids)
        return order_ids
Exemplo n.º 10
0
    def _get_or_pay(self, line, business_type,
                    partner_id, to_partner_id, name):
        if line.this_reconcile > line.to_reconcile:
            raise UserError(u'核销金额不能大于未核销金额')
        # 更新每一行的已核销余额、未核销余额
        line.name.to_reconcile -= line.this_reconcile
        line.name.reconciled += line.this_reconcile

        # 应收转应收、应付转应付
        if business_type in ['get_to_get', 'pay_to_pay']:
            if not float_is_zero(line.this_reconcile, 2):
                self.env['money.invoice'].create({
                       'name': name,
                       'category_id': line.category_id.id,
                       'amount': line.this_reconcile,
                       'date': line.date,
                       'reconciled': 0,  # 已核销金额
                       'to_reconcile': line.this_reconcile,  # 未核销金额
                       'date_due': line.date_due,
                       'partner_id': to_partner_id.id,
                       })

            if business_type == 'get_to_get':
                to_partner_id.receivable += line.this_reconcile
                partner_id.receivable -= line.this_reconcile
            if business_type == 'pay_to_pay':
                to_partner_id.payable += line.this_reconcile
                partner_id.payable -= line.this_reconcile

        return True
Exemplo n.º 11
0
    def _match_payment_to_invoice(self, order):
        account_precision = self.env['decimal.precision'].precision_get('Account')

        # ignore orders with an amount_paid of 0 because those are returns through the POS
        if not float_is_zero(order['amount_return'], account_precision) and not float_is_zero(order['amount_paid'], account_precision):
            cur_amount_paid = 0
            payments_to_keep = []
            for payment in order.get('statement_ids'):
                if cur_amount_paid + payment[2]['amount'] > order['amount_total']:
                    payment[2]['amount'] = order['amount_total'] - cur_amount_paid
                    payments_to_keep.append(payment)
                    break
                cur_amount_paid += payment[2]['amount']
                payments_to_keep.append(payment)
            order['statement_ids'] = payments_to_keep
            order['amount_return'] = 0
Exemplo n.º 12
0
        def _get_theorical_line(aml, theorical_lines, fields_list):
            # Search for a line matching the aml parameter.
            aml_currency = aml.currency_id or aml.company_currency_id
            for line in theorical_lines:
                field_index = 0
                match = True
                for f in fields_list:
                    line_value = line[field_index]
                    aml_value = getattr(aml, f.name)

                    if f.ttype == 'float':
                        if not float_is_zero(aml_value - line_value):
                            match = False
                            break
                    elif f.ttype == 'monetary':
                        if aml_currency.compare_amounts(aml_value, line_value):
                            match = False
                            break
                    elif f.ttype in ('one2many', 'many2many'):
                        if not sorted(aml_value.ids) == sorted(line_value or []):
                            match = False
                            break
                    elif f.ttype == 'many2one':
                        if (line_value or aml_value) and aml_value.id != line_value:
                            match = False
                            break
                    elif (line_value or aml_value) and line_value != aml_value:
                        match = False
                        break

                    field_index += 1
                if match:
                    return line
            return None
Exemplo n.º 13
0
    def _sale_get_invoice_price(self, order):
        """ Based on the current move line, compute the price to reinvoice the analytic line that is going to be created (so the
            price of the sale line).
        """
        self.ensure_one()

        unit_amount = self.quantity
        amount = (self.credit or 0.0) - (self.debit or 0.0)

        if self.product_id.expense_policy == 'sales_price':
            return self.product_id.with_context(
                partner=order.partner_id.id,
                date_order=order.date_order,
                pricelist=order.pricelist_id.id,
                uom=self.product_uom_id.id
            ).price

        uom_precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        if float_is_zero(unit_amount, precision_digits=uom_precision_digits):
            return 0.0

        # Prevent unnecessary currency conversion that could be impacted by exchange rate
        # fluctuations
        if self.company_id.currency_id and amount and self.company_id.currency_id == order.currency_id:
            return abs(amount / unit_amount)

        price_unit = abs(amount / unit_amount)
        currency_id = self.company_id.currency_id
        if currency_id and currency_id != order.currency_id:
            price_unit = currency_id._convert(price_unit, order.currency_id, order.company_id, order.date_order or fields.Date.today())
        return price_unit
Exemplo n.º 14
0
    def _get_or_pay(self, line, business_type,
                    partner_id, to_partner_id, name):
        decimal_amount = self.env.ref('core.decimal_amount')
        if float_compare(line.this_reconcile, line.to_reconcile, precision_digits=decimal_amount.digits) == 1:
            raise UserError(u'核销金额不能大于未核销金额!\n核销金额:%s 未核销金额:%s'%(line.this_reconcile, line.to_reconcile))
        # 更新每一行的已核销余额、未核销余额
        line.name.to_reconcile -= line.this_reconcile
        line.name.reconciled += line.this_reconcile

        # 应收转应收、应付转应付
        if business_type in ['get_to_get', 'pay_to_pay']:
            if not float_is_zero(line.this_reconcile, 2):
                self.env['money.invoice'].create({
                       'name': name,
                       'category_id': line.category_id.id,
                       'amount': line.this_reconcile,
                       'date': line.date,
                       'reconciled': 0,  # 已核销金额
                       'to_reconcile': line.this_reconcile,  # 未核销金额
                       'date_due': line.date_due,
                       'partner_id': to_partner_id.id,
                       })

            if business_type == 'get_to_get':
#                 to_partner_id.receivable += line.this_reconcile
                partner_id.receivable -= line.this_reconcile
            if business_type == 'pay_to_pay':
#                 to_partner_id.payable += line.this_reconcile
                partner_id.payable -= line.this_reconcile

        return True
Exemplo n.º 15
0
 def request_payment(self):
     categ = self.env.ref('money.core_category_purchase')
     if not float_is_zero(self.amount_money, 2):
         source_id = self.env['money.invoice'].create({
                             'name': self.buy_id.name,
                             'partner_id': self.buy_id.partner_id.id,
                             'category_id': categ.id,
                             'date': fields.Date.context_today(self),
                             'amount': self.amount_money,
                             'reconciled': 0,
                             'to_reconcile': self.amount_money,
                             'date_due': fields.Date.context_today(self),
                             'state': 'draft',
                         })
         self.with_context(type='pay').env["money.order"].create({
                             'partner_id': self.buy_id.partner_id.id,
                             'bank_name': self.buy_id.partner_id.bank_name,
                             'bank_num': self.buy_id.partner_id.bank_num,
                                 'date': fields.Date.context_today(self),
                                 'source_ids':
                                 [(0, 0, {'name':source_id.id,
                                  'category_id':categ.id,
                                  'date':source_id.date,
                                  'amount':self.amount_money,
                                  'reconciled':0.0,
                                  'to_reconcile':self.amount_money,
                                  'this_reconcile':self.amount_money})],
                                 'type': 'pay',
                                 'amount': self.amount_money,
                                 'reconciled': 0,
                                 'to_reconcile': self.amount_money,
                                 'state': 'draft',
             })
     self.date_application = datetime.now()
Exemplo n.º 16
0
 def data_handling(self, order_data):
     """准备创建 pos order 需要的数据"""
     payment_line_list = []
     line_data_list = [[0, 0, {'goods_id': line[2].get('product_id'),
                               'qty': line[2].get('qty'),
                               'price': line[2].get('price_unit'),
                               'discount_amount': line[2].get('discount')*\
                                                  line[2].get('price_unit') * line[2].get('qty')/100,
                               'discount_rate': line[2].get('discount'),
                               'subtotal': line[2].get('price_unit') * line[2].get('qty') - \
                                           line[2].get('discount') * line[2].get('price_unit') * line[2].get('qty')/100
                               }]
                       for line in order_data.get('lines')]
     prec_amt = self.env['decimal.precision'].precision_get('Amount')
     # 付款金额为0时不生成付款明细
     for line in order_data.get('statement_ids'):
         if not float_is_zero(line[2].get('amount'), precision_digits=prec_amt):
             payment_line_list.append((0, 0, {
                 'bank_account_id': line[2].get('statement_id'),
                 'amount': line[2].get('amount'),
                 'pay_date': line[2].get('name'),
             }))
     pos_order_data = dict(
         session_id=order_data.get('pos_session_id'),
         partner_id=order_data.get('partner_id') or self.env.ref('gooderp_pos.pos_partner').id,
         user_id=order_data.get('user_id') or 1,
         line_ids=line_data_list,
         date=order_data.get('creation_date'),
         payment_line_ids=payment_line_list,
     )
     return pos_order_data
Exemplo n.º 17
0
    def test_timesheet_manual(self):
        """ Test timesheet invoicing with 'invoice on delivery' timetracked products
        """
        # create SO and confirm it
        sale_order = self.env['sale.order'].create({
            'partner_id': self.partner_customer_usd.id,
            'partner_invoice_id': self.partner_customer_usd.id,
            'partner_shipping_id': self.partner_customer_usd.id,
            'pricelist_id': self.pricelist_usd.id,
        })
        so_line_manual_global_project = self.env['sale.order.line'].create({
            'name': self.product_delivery_manual2.name,
            'product_id': self.product_delivery_manual2.id,
            'product_uom_qty': 50,
            'product_uom': self.product_delivery_manual2.uom_id.id,
            'price_unit': self.product_delivery_manual2.list_price,
            'order_id': sale_order.id,
        })
        so_line_manual_only_project = self.env['sale.order.line'].create({
            'name': self.product_delivery_manual4.name,
            'product_id': self.product_delivery_manual4.id,
            'product_uom_qty': 20,
            'product_uom': self.product_delivery_manual4.uom_id.id,
            'price_unit': self.product_delivery_manual4.list_price,
            'order_id': sale_order.id,
        })

        # confirm SO
        sale_order.action_confirm()
        self.assertTrue(sale_order.project_ids, "Sales Order should have create a project")
        self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: manually product should not need to be invoiced on so confirmation')

        project_serv2 = so_line_manual_only_project.project_id
        self.assertTrue(project_serv2, "A second project is created when selling 'project only' after SO confirmation.")
        self.assertEqual(sale_order.analytic_account_id, project_serv2.analytic_account_id, "The created project should be linked to the analytic account of the SO")

        # let's log some timesheets (on task and project)
        timesheet1 = self.env['account.analytic.line'].create({
            'name': 'Test Line',
            'project_id': self.project_global.id,  # global project
            'task_id': so_line_manual_global_project.task_id.id,
            'unit_amount': 6,
            'employee_id': self.employee_manager.id,
        })

        timesheet2 = self.env['account.analytic.line'].create({
            'name': 'Test Line',
            'project_id': self.project_global.id,  # global project
            'unit_amount': 3,
            'employee_id': self.employee_manager.id,
        })

        self.assertEqual(len(sale_order.project_ids), 2, "One project should have been created by the SO, when confirmed + the one coming from SO line 1 'task in global project'.")
        self.assertEqual(so_line_manual_global_project.task_id.sale_line_id, so_line_manual_global_project, "Task from a milestone product should be linked to its SO line too")
        self.assertEqual(timesheet1.timesheet_invoice_type, 'billable_fixed', "Milestone timesheet goes in billable fixed category")
        self.assertTrue(float_is_zero(so_line_manual_global_project.qty_delivered, precision_digits=2), "Milestone Timesheeting should not incremented the delivered quantity on the SO line")
        self.assertEqual(so_line_manual_global_project.qty_to_invoice, 0.0, "Manual service should not be affected by timesheet on their created task.")
        self.assertEqual(so_line_manual_only_project.qty_to_invoice, 0.0, "Manual service should not be affected by timesheet on their created project.")
        self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation')
Exemplo n.º 18
0
    def action_invoice_create(self, grouped=False, final=False):
        """
        Create the invoice associated to the SO.
        :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by
                        (partner_invoice_id, currency)
        :param final: if True, refunds will be generated if necessary
        :returns: list of created invoices
        """
        inv_obj = self.env['account.invoice']
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        invoices = {}
        references = {}
        for order in self:
            group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id)
            for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
                if float_is_zero(line.qty_to_invoice, precision_digits=precision):
                    continue
                if group_key not in invoices:
                    inv_data = order._prepare_invoice()
                    invoice = inv_obj.create(inv_data)
                    references[invoice] = order
                    invoices[group_key] = invoice
                elif group_key in invoices:
                    vals = {}
                    if order.name not in invoices[group_key].origin.split(', '):
                        vals['origin'] = invoices[group_key].origin + ', ' + order.name
                    if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', '):
                        vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
                    invoices[group_key].write(vals)
                if line.qty_to_invoice > 0:
                    line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
                elif line.qty_to_invoice < 0 and final:
                    line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)

            if references.get(invoices.get(group_key)):
                if order not in references[invoices[group_key]]:
                    references[invoice] = references[invoice] | order

        if not invoices:
            raise UserError(_('There is no invoicable line.'))

        for invoice in invoices.values():
            if not invoice.invoice_line_ids:
                raise UserError(_('There is no invoicable line.'))
            # If invoice is negative, do a refund invoice instead
            if invoice.amount_untaxed < 0:
                invoice.type = 'out_refund'
                for line in invoice.invoice_line_ids:
                    line.quantity = -line.quantity
            # Use additional field helper function (for account extensions)
            for line in invoice.invoice_line_ids:
                line._set_additional_fields(invoice)
            # Necessary to force computation of taxes. In account_invoice, they are triggered
            # by onchanges, which are not triggered when doing a create.
            invoice.compute_taxes()
            invoice.message_post_with_view('mail.message_origin_link',
                values={'self': invoice, 'origin': references[invoice]},
                subtype_id=self.env.ref('mail.mt_note').id)
        return [inv.id for inv in invoices.values()]
Exemplo n.º 19
0
    def _process_order(self, pos_order):
        prec_acc = self.env["decimal.precision"].precision_get("Account")
        pos_session = self.env["pos.session"].browse(pos_order["pos_session_id"])
        if pos_session.state == "closing_control" or pos_session.state == "closed":
            pos_order["pos_session_id"] = self._get_valid_session(pos_order).id
        order = self.create(self._order_fields(pos_order))
        journal_ids = set()
        for payments in pos_order["statement_ids"]:
            if not float_is_zero(payments[2]["amount"], precision_digits=prec_acc):
                order.add_payment(self._payment_fields(payments[2]))
            journal_ids.add(payments[2]["journal_id"])

        if pos_session.sequence_number <= pos_order["sequence_number"]:
            pos_session.write({"sequence_number": pos_order["sequence_number"] + 1})
            pos_session.refresh()

        if not float_is_zero(pos_order["amount_return"], prec_acc):
            cash_journal_id = pos_session.cash_journal_id.id
            if not cash_journal_id:
                # Select for change one of the cash journals used in this
                # payment
                cash_journal = self.env["account.journal"].search(
                    [("type", "=", "cash"), ("id", "in", list(journal_ids))], limit=1
                )
                if not cash_journal:
                    # If none, select for change one of the cash journals of the POS
                    # This is used for example when a customer pays by credit card
                    # an amount higher than total amount of the order and gets cash back
                    cash_journal = [
                        statement.journal_id
                        for statement in pos_session.statement_ids
                        if statement.journal_id.type == "cash"
                    ]
                    if not cash_journal:
                        raise UserError(_("No cash statement found for this session. Unable to record returned cash."))
                cash_journal_id = cash_journal[0].id
            order.add_payment(
                {
                    "amount": -pos_order["amount_return"],
                    "payment_date": fields.Datetime.now(),
                    "payment_name": _("return"),
                    "journal": cash_journal_id,
                }
            )
        return order
Exemplo n.º 20
0
 def _buy_amount_to_invoice(self):
     '''采购费用产生结算单'''
     if sum(cost_line.amount for cost_line in self.cost_line_ids) > 0:
         for line in self.cost_line_ids:
             if not float_is_zero(line.amount,2):
                 self.env['money.invoice'].create(
                     self._get_invoice_vals(line.category_id,self.date, line.amount, 0)
                 )
     return
Exemplo n.º 21
0
    def _match_payment_to_invoice(self, order):
        account_precision = self.env["decimal.precision"].precision_get("Account")

        # ignore orders with an amount_paid of 0 because those are returns through the POS
        if not float_is_zero(order["amount_return"], account_precision) and not float_is_zero(
            order["amount_paid"], account_precision
        ):
            cur_amount_paid = 0
            payments_to_keep = []
            for payment in order.get("statement_ids"):
                if cur_amount_paid + payment[2]["amount"] > order["amount_total"]:
                    payment[2]["amount"] = order["amount_total"] - cur_amount_paid
                    payments_to_keep.append(payment)
                    break
                cur_amount_paid += payment[2]["amount"]
                payments_to_keep.append(payment)
            order["statement_ids"] = payments_to_keep
            order["amount_return"] = 0
Exemplo n.º 22
0
    def do_change_standard_price(self, new_price, account_id):
        """ Changes the Standard Price of Product and creates an account move accordingly."""
        AccountMove = self.env["account.move"]

        locations = self.env["stock.location"].search(
            [("usage", "=", "internal"), ("company_id", "=", self.env.user.company_id.id)]
        )

        product_accounts = {(product.id, product.product_tmpl_id.get_product_accounts()) for product in self}
        price_precision = dp.get_precision("Product Price")

        for location in locations:
            for product in self.with_context(location=location.id, compute_child=False):
                diff = product.standard_price - new_price
                if tools.float_is_zero(diff, precision_digits=price_precision):
                    raise UserError(_("No difference between standard price and new price!"))
                qty_available = product.qty_available
                if qty_available:
                    # Accounting Entries
                    if diff * qty_available > 0:
                        debit_account_id = account_id
                        credit_account_id = product_accounts[product.id]["stock_valuation"].id
                    else:
                        debit_account_id = product_accounts[product.id]["stock_valuation"].id
                        credit_account_id = account_id

                    move_vals = {
                        "journal_id": product_accounts[product.id]["stock_journal"].id,
                        "company_id": location.company_id.id,
                        "line_ids": [
                            (
                                0,
                                0,
                                {
                                    "name": _("Standard Price changed"),
                                    "account_id": debit_account_id,
                                    "debit": abs(diff * qty_available),
                                    "credit": 0,
                                },
                            ),
                            (
                                0,
                                0,
                                {
                                    "name": _("Standard Price changed"),
                                    "account_id": credit_account_id,
                                    "debit": 0,
                                    "credit": abs(diff * qty_available),
                                },
                            ),
                        ],
                    }
                    move = AccountMove.create(move_vals)
                    move.post()

        self.write({"standard_price": new_price})
        return True
Exemplo n.º 23
0
        def automatic_match_func(st_line, fetched_amls):
            # Match only one invoice.
            if len(fetched_amls) == 1:
                return True

            # Match multiple invoices but having the same total amount residual.
            total_residual = sum(aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in fetched_amls)
            line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
            line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
            return float_is_zero(total_residual - line_residual, precision_rounding=line_currency.rounding)
Exemplo n.º 24
0
 def _onchange_qty_done(self):
     """ When the user is encoding a produce line for a tracked product, we apply some logic to
     help him. This onchange will warn him if he set `qty_done` to a non-supported value.
     """
     res = {}
     if self.product_id.tracking == 'serial' and not float_is_zero(self.qty_done, self.product_uom_id.rounding):
         if float_compare(self.qty_done, 1.0, precision_rounding=self.product_uom_id.rounding) != 0:
             message = _('You can only process 1.0 %s of products with unique serial number.') % self.product_id.uom_id.name
             res['warning'] = {'title': _('Warning'), 'message': message}
     return res
Exemplo n.º 25
0
 def _sell_amount_to_invoice(self):
     '''销售费用产生结算单'''
     invoice_id = False
     if sum(cost_line.amount for cost_line in self.cost_line_ids) > 0:
         for line in self.cost_line_ids:
             if not float_is_zero(line.amount,2):
                 invoice_id = self.env['money.invoice'].create(
                     self._get_invoice_vals(line.partner_id, line.category_id, self.date, line.amount + line.tax, line.tax)
                 )
     return invoice_id
Exemplo n.º 26
0
        def automatic_match_func(st_line, fetched_amls):
            total_residual = sum(aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in fetched_amls)
            line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
            line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id

            # Match the total residual amount.
            if float_is_zero(total_residual - line_residual, precision_rounding=line_currency.rounding):
                return True

            # Match only one line having a greater residual amount.
            return len(fetched_amls) == 1 and line_residual < total_residual
Exemplo n.º 27
0
 def _generate_moves(self):
     vals_list = []
     for line in self:
         rounding = line.product_id.uom_id.rounding
         if float_is_zero(line.difference_qty, precision_rounding=rounding):
             continue
         if line.difference_qty > 0:  # found more than expected
             vals = line._get_move_values(line.difference_qty, line.product_id.property_stock_inventory.id, line.location_id.id, False)
         else:
             vals = line._get_move_values(abs(line.difference_qty), line.location_id.id, line.product_id.property_stock_inventory.id, True)
         vals_list.append(vals)
     return self.env['stock.move'].create(vals_list)
Exemplo n.º 28
0
    def _process_order(self, pos_order):
        prec_acc = self.env['decimal.precision'].precision_get('Account')
        pos_session = self.env['pos.session'].browse(pos_order['pos_session_id'])
        if pos_session.state == 'closing_control' or pos_session.state == 'closed':
            pos_order['pos_session_id'] = self._get_valid_session(pos_order).id
        order = self.create(self._order_fields(pos_order))
        journal_ids = set()
        for payments in pos_order['statement_ids']:
            if not float_is_zero(payments[2]['amount'], precision_digits=prec_acc):
                order.add_payment(self._payment_fields(payments[2]))
            journal_ids.add(payments[2]['journal_id'])

        if pos_session.sequence_number <= pos_order['sequence_number']:
            pos_session.write({'sequence_number': pos_order['sequence_number'] + 1})
            pos_session.refresh()

        if not float_is_zero(pos_order['amount_return'], prec_acc):
            cash_journal_id = pos_session.cash_journal_id.id
            if not cash_journal_id:
                # Select for change one of the cash journals used in this
                # payment
                cash_journal = self.env['account.journal'].search([
                    ('type', '=', 'cash'),
                    ('id', 'in', list(journal_ids)),
                ], limit=1)
                if not cash_journal:
                    # If none, select for change one of the cash journals of the POS
                    # This is used for example when a customer pays by credit card
                    # an amount higher than total amount of the order and gets cash back
                    cash_journal = [statement.journal_id for statement in pos_session.statement_ids if statement.journal_id.type == 'cash']
                    if not cash_journal:
                        raise UserError(_("No cash statement found for this session. Unable to record returned cash."))
                cash_journal_id = cash_journal[0].id
            order.add_payment({
                'amount': -pos_order['amount_return'],
                'payment_date': fields.Datetime.now(),
                'payment_name': _('return'),
                'journal': cash_journal_id,
            })
        return order
Exemplo n.º 29
0
    def is_zero(self, amount):
        """Returns true if ``amount`` is small enough to be treated as
           zero according to current currency's rounding rules.
           Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
           ``compare_amounts(amount1,amount2) == 0``, as the former will round after
           computing the difference, while the latter will round before, giving
           different results for e.g. 0.006 and 0.002 at 2 digits precision.

           :param float amount: amount to compare with currency's zero

           With the new API, call it like: ``currency.is_zero(amount)``.
        """
        return tools.float_is_zero(amount, precision_rounding=self.rounding)
Exemplo n.º 30
0
 def make_invoice(self):
     '''生成结算单'''
     for line in self:
         invoice_id = False
         if not line.project_id.customer_id:
             raise UserError(u'生成发票前请输入客户')
         category = self.env.ref('money.core_category_sale')
         if not float_is_zero(self.amount, 2):
             invoice_id = self.env['money.invoice'].create(
                 self._get_invoice_vals(category, line.project_id, line.amount, line.tax_amount)
             )
             line.invoice_id = invoice_id.id
         return invoice_id
Exemplo n.º 31
0
 def pre_process_parsed_inv(self, parsed_inv):
     if parsed_inv.get('pre-processed'):
         return parsed_inv
     parsed_inv['pre-processed'] = True
     if 'chatter_msg' not in parsed_inv:
         parsed_inv['chatter_msg'] = []
     if parsed_inv.get('type') in ('out_invoice', 'out_refund'):
         return parsed_inv
     prec_ac = self.env['decimal.precision'].precision_get('Account')
     prec_pp = self.env['decimal.precision'].precision_get('Product Price')
     prec_uom = self.env['decimal.precision'].precision_get(
         'Product Unit of Measure')
     if 'amount_tax' in parsed_inv and 'amount_untaxed' not in parsed_inv:
         parsed_inv['amount_untaxed'] =\
             parsed_inv['amount_total'] - parsed_inv['amount_tax']
     elif ('amount_untaxed' not in parsed_inv
           and 'amount_tax' not in parsed_inv):
         # For invoices that never have taxes
         parsed_inv['amount_untaxed'] = parsed_inv['amount_total']
     # Support the 2 refund methods; if method a) is used, we convert to
     # method b)
     if not parsed_inv.get('type'):
         parsed_inv['type'] = 'in_invoice'  # default value
     if (parsed_inv['type'] == 'in_invoice' and float_compare(
             parsed_inv['amount_total'], 0, precision_digits=prec_ac) < 0):
         parsed_inv['type'] = 'in_refund'
         for entry in ['amount_untaxed', 'amount_total']:
             parsed_inv[entry] *= -1
         for line in parsed_inv.get('lines', []):
             line['qty'] *= -1
             if 'price_subtotal' in line:
                 line['price_subtotal'] *= -1
     # Handle the case where we import an invoice with VAT in a company that
     # cannot deduct VAT
     if self.company_cannot_refund_vat():
         parsed_inv['amount_tax'] = 0
         parsed_inv['amount_untaxed'] = parsed_inv['amount_total']
         for line in parsed_inv.get('lines', []):
             if line.get('taxes'):
                 if len(line['taxes']) > 1:
                     raise UserError(
                         _("You are importing an invoice in a company that "
                           "cannot deduct VAT and the imported invoice has "
                           "several VAT taxes on the same line (%s). We do "
                           "not support this scenario for the moment.") %
                         line.get('name'))
                 vat_rate = line['taxes'][0].get('amount')
                 if not float_is_zero(vat_rate, precision_digits=2):
                     line['price_unit'] = line['price_unit'] *\
                         (1 + vat_rate/100.0)
                     line.pop('price_subtotal')
                     line['taxes'] = []
     # Rounding work
     for entry in ['amount_untaxed', 'amount_total']:
         parsed_inv[entry] = float_round(parsed_inv[entry],
                                         precision_digits=prec_ac)
     for line in parsed_inv.get('lines', []):
         line['qty'] = float_round(line['qty'], precision_digits=prec_uom)
         line['price_unit'] = float_round(line['price_unit'],
                                          precision_digits=prec_pp)
     logger.debug('Result of invoice parsing parsed_inv=%s', parsed_inv)
     # the 'company' dict in parsed_inv is NOT used to auto-detect
     # the company, but to check that we are not importing an
     # invoice for another company by mistake
     # The advantage of doing the check here is that it will be run
     # in all scenarios (create/update/...), but it's not related
     # to invoice parsing...
     if (parsed_inv.get('company') and not config['test_enable']
             and not self.env.context.get('edi_skip_company_check')):
         self.env['business.document.import']._check_company(
             parsed_inv['company'], parsed_inv['chatter_msg'])
     return parsed_inv
Exemplo n.º 32
0
    def generate_fec(self):
        self.ensure_one()
        # We choose to implement the flat file instead of the XML
        # file for 2 reasons :
        # 1) the XSD file impose to have the label on the account.move
        # but Odoo has the label on the account.move.line, so that's a
        # problem !
        # 2) CSV files are easier to read/use for a regular accountant.
        # So it will be easier for the accountant to check the file before
        # sending it to the fiscal administration
        today = fields.Date.today()
        if self.date_from > today or self.date_to > today:
            raise UserError(
                _('You could not set the start date or the end date in the future.'
                  ))
        if self.date_from >= self.date_to:
            raise UserError(
                _('The start date must be inferior to the end date.'))

        company = self.env.company
        company_legal_data = self._get_company_legal_data(company)

        header = [
            u'JournalCode',  # 0
            u'JournalLib',  # 1
            u'EcritureNum',  # 2
            u'EcritureDate',  # 3
            u'CompteNum',  # 4
            u'CompteLib',  # 5
            u'CompAuxNum',  # 6  We use partner.id
            u'CompAuxLib',  # 7
            u'PieceRef',  # 8
            u'PieceDate',  # 9
            u'EcritureLib',  # 10
            u'Debit',  # 11
            u'Credit',  # 12
            u'EcritureLet',  # 13
            u'DateLet',  # 14
            u'ValidDate',  # 15
            u'Montantdevise',  # 16
            u'Idevise',  # 17
        ]

        rows_to_write = [header]
        # INITIAL BALANCE
        unaffected_earnings_xml_ref = self.env.ref(
            'account.data_unaffected_earnings')
        unaffected_earnings_line = True  # used to make sure that we add the unaffected earning initial balance only once
        if unaffected_earnings_xml_ref:
            #compute the benefit/loss of last year to add in the initial balance of the current year earnings account
            unaffected_earnings_results = self.do_query_unaffected_earnings()
            unaffected_earnings_line = False

        sql_query = '''
        SELECT
            'OUV' AS JournalCode,
            'Balance initiale' AS JournalLib,
            'OUVERTURE/' || %s AS EcritureNum,
            %s AS EcritureDate,
            MIN(aa.code) AS CompteNum,
            replace(replace(MIN(aa.name), '|', '/'), '\t', '') AS CompteLib,
            '' AS CompAuxNum,
            '' AS CompAuxLib,
            '-' AS PieceRef,
            %s AS PieceDate,
            '/' AS EcritureLib,
            replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit,
            replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit,
            '' AS EcritureLet,
            '' AS DateLet,
            %s AS ValidDate,
            '' AS Montantdevise,
            '' AS Idevise,
            MIN(aa.id) AS CompteID
        FROM
            account_move_line aml
            LEFT JOIN account_move am ON am.id=aml.move_id
            JOIN account_account aa ON aa.id = aml.account_id
            LEFT JOIN account_account_type aat ON aa.user_type_id = aat.id
        WHERE
            am.date < %s
            AND am.company_id = %s
            AND aat.include_initial_balance = 't'
            AND (aml.debit != 0 OR aml.credit != 0)
        '''

        # For official report: only use posted entries
        if self.export_type == "official":
            sql_query += '''
            AND am.state = 'posted'
            '''

        sql_query += '''
        GROUP BY aml.account_id, aat.type
        HAVING round(sum(aml.balance), %s) != 0
        AND aat.type not in ('receivable', 'payable')
        '''
        formatted_date_from = fields.Date.to_string(self.date_from).replace(
            '-', '')
        date_from = self.date_from
        formatted_date_year = date_from.year
        currency_digits = 2

        self._cr.execute(
            sql_query,
            (formatted_date_year, formatted_date_from, formatted_date_from,
             formatted_date_from, self.date_from, company.id, currency_digits))

        for row in self._cr.fetchall():
            listrow = list(row)
            account_id = listrow.pop()
            if not unaffected_earnings_line:
                account = self.env['account.account'].browse(account_id)
                if account.user_type_id.id == self.env.ref(
                        'account.data_unaffected_earnings').id:
                    #add the benefit/loss of previous fiscal year to the first unaffected earnings account found.
                    unaffected_earnings_line = True
                    current_amount = float(listrow[11].replace(
                        ',', '.')) - float(listrow[12].replace(',', '.'))
                    unaffected_earnings_amount = float(
                        unaffected_earnings_results[11].replace(
                            ',', '.')) - float(
                                unaffected_earnings_results[12].replace(
                                    ',', '.'))
                    listrow_amount = current_amount + unaffected_earnings_amount
                    if float_is_zero(listrow_amount,
                                     precision_digits=currency_digits):
                        continue
                    if listrow_amount > 0:
                        listrow[11] = str(listrow_amount).replace('.', ',')
                        listrow[12] = '0,00'
                    else:
                        listrow[11] = '0,00'
                        listrow[12] = str(-listrow_amount).replace('.', ',')
            rows_to_write.append(listrow)

        #if the unaffected earnings account wasn't in the selection yet: add it manually
        if (not unaffected_earnings_line and unaffected_earnings_results
                and (unaffected_earnings_results[11] != '0,00'
                     or unaffected_earnings_results[12] != '0,00')):
            #search an unaffected earnings account
            unaffected_earnings_account = self.env['account.account'].search(
                [('user_type_id', '=',
                  self.env.ref('account.data_unaffected_earnings').id)],
                limit=1)
            if unaffected_earnings_account:
                unaffected_earnings_results[
                    4] = unaffected_earnings_account.code
                unaffected_earnings_results[
                    5] = unaffected_earnings_account.name
            rows_to_write.append(unaffected_earnings_results)

        # INITIAL BALANCE - receivable/payable
        sql_query = '''
        SELECT
            'OUV' AS JournalCode,
            'Balance initiale' AS JournalLib,
            'OUVERTURE/' || %s AS EcritureNum,
            %s AS EcritureDate,
            MIN(aa.code) AS CompteNum,
            replace(MIN(aa.name), '|', '/') AS CompteLib,
            CASE WHEN MIN(aat.type) IN ('receivable', 'payable')
            THEN
                CASE WHEN rp.ref IS null OR rp.ref = ''
                THEN rp.id::text
                ELSE replace(rp.ref, '|', '/')
                END
            ELSE ''
            END
            AS CompAuxNum,
            CASE WHEN aat.type IN ('receivable', 'payable')
            THEN COALESCE(replace(rp.name, '|', '/'), '')
            ELSE ''
            END AS CompAuxLib,
            '-' AS PieceRef,
            %s AS PieceDate,
            '/' AS EcritureLib,
            replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit,
            replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit,
            '' AS EcritureLet,
            '' AS DateLet,
            %s AS ValidDate,
            '' AS Montantdevise,
            '' AS Idevise,
            MIN(aa.id) AS CompteID
        FROM
            account_move_line aml
            LEFT JOIN account_move am ON am.id=aml.move_id
            LEFT JOIN res_partner rp ON rp.id=aml.partner_id
            JOIN account_account aa ON aa.id = aml.account_id
            LEFT JOIN account_account_type aat ON aa.user_type_id = aat.id
        WHERE
            am.date < %s
            AND am.company_id = %s
            AND aat.include_initial_balance = 't'
            AND (aml.debit != 0 OR aml.credit != 0)
        '''

        # For official report: only use posted entries
        if self.export_type == "official":
            sql_query += '''
            AND am.state = 'posted'
            '''

        sql_query += '''
        GROUP BY aml.account_id, aat.type, rp.ref, rp.id
        HAVING round(sum(aml.balance), %s) != 0
        AND aat.type in ('receivable', 'payable')
        '''
        self._cr.execute(
            sql_query,
            (formatted_date_year, formatted_date_from, formatted_date_from,
             formatted_date_from, self.date_from, company.id, currency_digits))

        for row in self._cr.fetchall():
            listrow = list(row)
            account_id = listrow.pop()
            rows_to_write.append(listrow)

        # LINES
        sql_query = '''
        SELECT
            replace(replace(aj.code, '|', '/'), '\t', '') AS JournalCode,
            replace(replace(aj.name, '|', '/'), '\t', '') AS JournalLib,
            replace(replace(am.name, '|', '/'), '\t', '') AS EcritureNum,
            TO_CHAR(am.date, 'YYYYMMDD') AS EcritureDate,
            aa.code AS CompteNum,
            replace(replace(aa.name, '|', '/'), '\t', '') AS CompteLib,
            CASE WHEN aat.type IN ('receivable', 'payable')
            THEN
                CASE WHEN rp.ref IS null OR rp.ref = ''
                THEN rp.id::text
                ELSE replace(rp.ref, '|', '/')
                END
            ELSE ''
            END
            AS CompAuxNum,
            CASE WHEN aat.type IN ('receivable', 'payable')
            THEN COALESCE(replace(replace(rp.name, '|', '/'), '\t', ''), '')
            ELSE ''
            END AS CompAuxLib,
            CASE WHEN am.ref IS null OR am.ref = ''
            THEN '-'
            ELSE replace(replace(am.ref, '|', '/'), '\t', '')
            END
            AS PieceRef,
            TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate,
            CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/'
                WHEN aml.name SIMILAR TO '[\t|\s|\n]*' THEN '/'
                ELSE replace(replace(replace(replace(aml.name, '|', '/'), '\t', ''), '\n', ''), '\r', '') END AS EcritureLib,
            replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
            replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
            CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
            CASE WHEN aml.full_reconcile_id IS NULL THEN '' ELSE TO_CHAR(rec.create_date, 'YYYYMMDD') END AS DateLet,
            TO_CHAR(am.date, 'YYYYMMDD') AS ValidDate,
            CASE
                WHEN aml.amount_currency IS NULL OR aml.amount_currency = 0 THEN ''
                ELSE replace(to_char(aml.amount_currency, '000000000000000D99'), '.', ',')
            END AS Montantdevise,
            CASE WHEN aml.currency_id IS NULL THEN '' ELSE rc.name END AS Idevise
        FROM
            account_move_line aml
            LEFT JOIN account_move am ON am.id=aml.move_id
            LEFT JOIN res_partner rp ON rp.id=aml.partner_id
            JOIN account_journal aj ON aj.id = am.journal_id
            JOIN account_account aa ON aa.id = aml.account_id
            LEFT JOIN account_account_type aat ON aa.user_type_id = aat.id
            LEFT JOIN res_currency rc ON rc.id = aml.currency_id
            LEFT JOIN account_full_reconcile rec ON rec.id = aml.full_reconcile_id
        WHERE
            am.date >= %s
            AND am.date <= %s
            AND am.company_id = %s
            AND (aml.debit != 0 OR aml.credit != 0)
        '''

        # For official report: only use posted entries
        if self.export_type == "official":
            sql_query += '''
            AND am.state = 'posted'
            '''

        sql_query += '''
        ORDER BY
            am.date,
            am.name,
            aml.id
        '''
        self._cr.execute(sql_query, (self.date_from, self.date_to, company.id))

        for row in self._cr.fetchall():
            rows_to_write.append(list(row))

        fecvalue = self._csv_write_rows(rows_to_write)
        end_date = fields.Date.to_string(self.date_to).replace('-', '')
        suffix = ''
        if self.export_type == "nonofficial":
            suffix = '-NONOFFICIAL'

        self.write({
            'fec_data':
            base64.encodebytes(fecvalue),
            # Filename = <siren>FECYYYYMMDD where YYYMMDD is the closing date
            'filename':
            '%sFEC%s%s.csv' % (company_legal_data['siren'], end_date, suffix),
        })

        # Set fiscal year lock date to the end date (not in test)
        fiscalyear_lock_date = self.env.company.fiscalyear_lock_date
        if not self.test_file and (not fiscalyear_lock_date
                                   or fiscalyear_lock_date < self.date_to):
            self.env.company.write({'fiscalyear_lock_date': self.date_to})
        return {
            'name':
            'FEC',
            'type':
            'ir.actions.act_url',
            'url':
            "web/content/?model=account.fr.fec&id=" + str(self.id) +
            "&filename_field=filename&field=fec_data&download=true&filename=" +
            self.filename,
            'target':
            'self',
        }
Exemplo n.º 33
0
 def update_invoice_lines(self, parsed_inv, invoice, seller):
     chatter = parsed_inv['chatter_msg']
     ailo = self.env['account.invoice.line']
     dpo = self.env['decimal.precision']
     qty_prec = dpo.precision_get('Product Unit of Measure')
     existing_lines = []
     for eline in invoice.invoice_line_ids:
         price_unit = 0.0
         if not float_is_zero(eline.quantity, precision_digits=qty_prec):
             price_unit = eline.price_subtotal / float(eline.quantity)
         existing_lines.append({
             'product': eline.product_id or False,
             'name': eline.name,
             'qty': eline.quantity,
             'uom': eline.uom_id,
             'line': eline,
             'price_unit': price_unit,
         })
     compare_res = self.env['business.document.import'].compare_lines(
         existing_lines, parsed_inv['lines'], chatter, seller=seller)
     if not compare_res:
         return
     for eline, cdict in list(compare_res['to_update'].items()):
         write_vals = {}
         if cdict.get('qty'):
             chatter.append(
                 _("The quantity has been updated on the invoice line "
                   "with product '%s' from %s to %s %s") %
                 (eline.product_id.display_name, cdict['qty'][0],
                  cdict['qty'][1], eline.uom_id.name))
             write_vals['quantity'] = cdict['qty'][1]
         if cdict.get('price_unit'):
             chatter.append(
                 _("The unit price has been updated on the invoice "
                   "line with product '%s' from %s to %s %s") % (
                       eline.product_id.display_name,
                       eline.price_unit,
                       cdict['price_unit'][1],  # TODO fix
                       invoice.currency_id.name))
             write_vals['price_unit'] = cdict['price_unit'][1]
         if write_vals:
             eline.write(write_vals)
     if compare_res['to_remove']:
         to_remove_label = [
             '%s %s x %s' % (l.quantity, l.uom_id.name, l.product_id.name)
             for l in compare_res['to_remove']
         ]
         chatter.append(
             _("%d invoice line(s) deleted: %s") %
             (len(compare_res['to_remove']), ', '.join(to_remove_label)))
         compare_res['to_remove'].unlink()
     if compare_res['to_add']:
         to_create_label = []
         for add in compare_res['to_add']:
             line_vals = self._prepare_create_invoice_line(
                 add['product'], add['uom'], add['import_line'], invoice)
             new_line = ailo.create(line_vals)
             to_create_label.append(
                 '%s %s x %s' %
                 (new_line.quantity, new_line.uom_id.name, new_line.name))
         chatter.append(
             _("%d new invoice line(s) created: %s") %
             (len(compare_res['to_add']), ', '.join(to_create_label)))
     invoice.compute_taxes()
     return True
Exemplo n.º 34
0
    def test_timesheet_delivery(self):
        """ Test timesheet invoicing with 'invoice on delivery' timetracked products
                1. Create SO and confirm it
                2. log timesheet
                3. create invoice
                4. log other timesheet
                5. create a second invoice
                6. add new SO line (delivered service)
        """
        # create SO and confirm it
        sale_order = self.env['sale.order'].create({
            'partner_id':
            self.partner_a.id,
            'partner_invoice_id':
            self.partner_a.id,
            'partner_shipping_id':
            self.partner_a.id,
            'pricelist_id':
            self.company_data['default_pricelist'].id,
        })
        so_line_deliver_global_project = self.env['sale.order.line'].create({
            'name':
            self.product_delivery_timesheet2.name,
            'product_id':
            self.product_delivery_timesheet2.id,
            'product_uom_qty':
            50,
            'product_uom':
            self.product_delivery_timesheet2.uom_id.id,
            'price_unit':
            self.product_delivery_timesheet2.list_price,
            'order_id':
            sale_order.id,
        })
        so_line_deliver_task_project = self.env['sale.order.line'].create({
            'name':
            self.product_delivery_timesheet3.name,
            'product_id':
            self.product_delivery_timesheet3.id,
            'product_uom_qty':
            20,
            'product_uom':
            self.product_delivery_timesheet3.uom_id.id,
            'price_unit':
            self.product_delivery_timesheet3.list_price,
            'order_id':
            sale_order.id,
        })
        so_line_deliver_global_project.product_id_change()
        so_line_deliver_task_project.product_id_change()

        # confirm SO
        sale_order.action_confirm()
        task_serv1 = self.env['project.task'].search([
            ('sale_line_id', '=', so_line_deliver_global_project.id)
        ])
        task_serv2 = self.env['project.task'].search([
            ('sale_line_id', '=', so_line_deliver_task_project.id)
        ])
        project_serv2 = self.env['project.project'].search([
            ('sale_line_id', '=', so_line_deliver_task_project.id)
        ])

        self.assertEqual(
            task_serv1.project_id, self.project_global,
            "Sale Timesheet: task should be created in global project")
        self.assertTrue(
            task_serv1,
            "Sale Timesheet: on SO confirmation, a task should have been created in global project"
        )
        self.assertTrue(
            task_serv2,
            "Sale Timesheet: on SO confirmation, a task should have been created in a new project"
        )
        self.assertEqual(
            sale_order.invoice_status, 'no',
            'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation'
        )
        self.assertEqual(sale_order.analytic_account_id,
                         task_serv2.project_id.analytic_account_id,
                         "SO should have create a project")
        self.assertEqual(
            sale_order.tasks_count, 2,
            "Two tasks (1 per SO line) should have been created on SO confirmation"
        )
        self.assertEqual(
            len(sale_order.project_ids), 2,
            "One project should have been created by the SO, when confirmed + the one from SO line 1 'task in global project'"
        )
        self.assertEqual(
            sale_order.analytic_account_id, project_serv2.analytic_account_id,
            "The created project should be linked to the analytic account of the SO"
        )

        # let's log some timesheets
        timesheet1 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            task_serv1.project_id.id,  # global project
            'task_id':
            task_serv1.id,
            'unit_amount':
            10.5,
            'employee_id':
            self.employee_manager.id,
        })
        self.assertEqual(
            so_line_deliver_global_project.invoice_status, 'to invoice',
            'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged'
        )
        self.assertEqual(
            so_line_deliver_task_project.invoice_status, 'no',
            'Sale Timesheet: so line invoice status should not change when no timesheet linked to the line'
        )
        self.assertEqual(
            sale_order.invoice_status, 'to invoice',
            'Sale Timesheet: "invoice on delivery" timesheets should set the so in "to invoice" status when logged'
        )
        self.assertEqual(
            timesheet1.timesheet_invoice_type, 'billable_time',
            "Timesheets linked to SO line with delivered product shoulbe be billable time"
        )
        self.assertFalse(
            timesheet1.timesheet_invoice_id,
            "The timesheet1 should not be linked to the invoice yet")

        # invoice SO
        invoice1 = sale_order._create_invoices()
        self.assertTrue(
            float_is_zero(invoice1.amount_total -
                          so_line_deliver_global_project.price_unit * 10.5,
                          precision_digits=2),
            'Sale: invoice generation on timesheets product is wrong')
        self.assertEqual(
            timesheet1.timesheet_invoice_id, invoice1,
            "The timesheet1 should not be linked to the invoice 1, as we are in delivered quantity (even if invoice is in draft"
        )
        with self.assertRaises(
                UserError
        ):  # We can not modify timesheet linked to invoice (even draft ones)
            timesheet1.write({'unit_amount': 42})

        # log some timesheets again
        timesheet2 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            task_serv1.project_id.id,  # global project
            'task_id':
            task_serv1.id,
            'unit_amount':
            39.5,
            'employee_id':
            self.employee_user.id,
        })
        self.assertEqual(
            so_line_deliver_global_project.invoice_status, 'to invoice',
            'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged'
        )
        self.assertEqual(
            so_line_deliver_task_project.invoice_status, 'no',
            'Sale Timesheet: so line invoice status should not change when no timesheet linked to the line'
        )
        self.assertEqual(
            sale_order.invoice_status, 'to invoice',
            'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so'
        )
        self.assertEqual(
            timesheet2.timesheet_invoice_type, 'billable_time',
            "Timesheets linked to SO line with delivered product shoulbe be billable time"
        )
        self.assertFalse(
            timesheet2.timesheet_invoice_id,
            "The timesheet2 should not be linked to the invoice yet")

        # create a second invoice
        invoice2 = sale_order._create_invoices()[0]
        self.assertEqual(
            len(sale_order.invoice_ids), 2,
            "A second invoice should have been created from the SO")
        self.assertEqual(
            so_line_deliver_global_project.invoice_status, 'invoiced',
            'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged'
        )
        self.assertEqual(
            sale_order.invoice_status, 'no',
            'Sale Timesheet: "invoice on delivery" timesheets should be invoiced completely by now'
        )
        self.assertEqual(
            timesheet2.timesheet_invoice_id, invoice2,
            "The timesheet2 should not be linked to the invoice 2")
        with self.assertRaises(
                UserError
        ):  # We can not modify timesheet linked to invoice (even draft ones)
            timesheet2.write({'unit_amount': 42})

        # add a line on SO
        so_line_deliver_only_project = self.env['sale.order.line'].create({
            'name':
            self.product_delivery_timesheet4.name,
            'product_id':
            self.product_delivery_timesheet4.id,
            'product_uom_qty':
            5,
            'product_uom':
            self.product_delivery_timesheet4.uom_id.id,
            'price_unit':
            self.product_delivery_timesheet4.list_price,
            'order_id':
            sale_order.id,
        })
        self.assertEqual(
            len(sale_order.project_ids), 2,
            "No new project should have been created by the SO, when selling 'project only' product, since it reuse the one from 'new task in new project'."
        )

        # let's log some timesheets on the project
        timesheet3 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            project_serv2.id,
            'unit_amount':
            7,
            'employee_id':
            self.employee_user.id,
        })
        self.assertTrue(
            float_is_zero(so_line_deliver_only_project.qty_delivered,
                          precision_digits=2),
            "Timesheeting on project should not incremented the delivered quantity on the SO line"
        )
        self.assertEqual(
            sale_order.invoice_status, 'to invoice',
            'Sale Timesheet: "invoice on delivery" timesheets should have quantity to invoice'
        )
        self.assertEqual(
            timesheet3.timesheet_invoice_type, 'non_billable_project',
            "Timesheets without task shoulbe be 'no project found'")
        self.assertFalse(
            timesheet3.timesheet_invoice_id,
            "The timesheet3 should not be linked to the invoice yet")

        # let's log some timesheets on the task (new task/new project)
        timesheet4 = self.env['account.analytic.line'].create({
            'name':
            'Test Line 4',
            'project_id':
            task_serv2.project_id.id,
            'task_id':
            task_serv2.id,
            'unit_amount':
            7,
            'employee_id':
            self.employee_user.id,
        })
        self.assertFalse(
            timesheet4.timesheet_invoice_id,
            "The timesheet4 should not be linked to the invoice yet")

        # modify a non invoiced timesheet
        timesheet4.write({'unit_amount': 42})

        self.assertFalse(
            timesheet4.timesheet_invoice_id,
            "The timesheet4 should not still be linked to the invoice")

        # validate the second invoice
        invoice2.action_post()

        self.assertEqual(
            timesheet1.timesheet_invoice_id, invoice1,
            "The timesheet1 should not be linked to the invoice 1, even after validation"
        )
        self.assertEqual(
            timesheet2.timesheet_invoice_id, invoice2,
            "The timesheet2 should not be linked to the invoice 1, even after validation"
        )
        self.assertFalse(
            timesheet3.timesheet_invoice_id,
            "The timesheet3 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet4.timesheet_invoice_id,
            "The timesheet4 should not be linked to the invoice, since we are in ordered quantity"
        )
Exemplo n.º 35
0
    def test_timesheet_manual(self):
        """ Test timesheet invoicing with 'invoice on delivery' timetracked products
        """
        # create SO and confirm it
        sale_order = self.env['sale.order'].create({
            'partner_id':
            self.partner_a.id,
            'partner_invoice_id':
            self.partner_a.id,
            'partner_shipping_id':
            self.partner_a.id,
            'pricelist_id':
            self.company_data['default_pricelist'].id,
        })
        so_line_manual_global_project = self.env['sale.order.line'].create({
            'name':
            self.product_delivery_manual2.name,
            'product_id':
            self.product_delivery_manual2.id,
            'product_uom_qty':
            50,
            'product_uom':
            self.product_delivery_manual2.uom_id.id,
            'price_unit':
            self.product_delivery_manual2.list_price,
            'order_id':
            sale_order.id,
        })
        so_line_manual_only_project = self.env['sale.order.line'].create({
            'name':
            self.product_delivery_manual4.name,
            'product_id':
            self.product_delivery_manual4.id,
            'product_uom_qty':
            20,
            'product_uom':
            self.product_delivery_manual4.uom_id.id,
            'price_unit':
            self.product_delivery_manual4.list_price,
            'order_id':
            sale_order.id,
        })

        # confirm SO
        sale_order.action_confirm()
        self.assertTrue(sale_order.project_ids,
                        "Sales Order should have create a project")
        self.assertEqual(
            sale_order.invoice_status, 'no',
            'Sale Timesheet: manually product should not need to be invoiced on so confirmation'
        )

        project_serv2 = so_line_manual_only_project.project_id
        self.assertTrue(
            project_serv2,
            "A second project is created when selling 'project only' after SO confirmation."
        )
        self.assertEqual(
            sale_order.analytic_account_id, project_serv2.analytic_account_id,
            "The created project should be linked to the analytic account of the SO"
        )

        # let's log some timesheets (on task and project)
        timesheet1 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            self.project_global.id,  # global project
            'task_id':
            so_line_manual_global_project.task_id.id,
            'unit_amount':
            6,
            'employee_id':
            self.employee_manager.id,
        })

        timesheet2 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            self.project_global.id,  # global project
            'unit_amount':
            3,
            'employee_id':
            self.employee_manager.id,
        })

        self.assertEqual(
            len(sale_order.project_ids), 2,
            "One project should have been created by the SO, when confirmed + the one coming from SO line 1 'task in global project'."
        )
        self.assertEqual(
            so_line_manual_global_project.task_id.sale_line_id,
            so_line_manual_global_project,
            "Task from a milestone product should be linked to its SO line too"
        )
        self.assertEqual(
            timesheet1.timesheet_invoice_type, 'billable_fixed',
            "Milestone timesheet goes in billable fixed category")
        self.assertTrue(
            float_is_zero(so_line_manual_global_project.qty_delivered,
                          precision_digits=2),
            "Milestone Timesheeting should not incremented the delivered quantity on the SO line"
        )
        self.assertEqual(
            so_line_manual_global_project.qty_to_invoice, 0.0,
            "Manual service should not be affected by timesheet on their created task."
        )
        self.assertEqual(
            so_line_manual_only_project.qty_to_invoice, 0.0,
            "Manual service should not be affected by timesheet on their created project."
        )
        self.assertEqual(
            sale_order.invoice_status, 'no',
            'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation'
        )

        self.assertEqual(
            timesheet1.timesheet_invoice_type, 'billable_fixed',
            "Timesheets linked to SO line with ordered product shoulbe be billable fixed since it is a milestone"
        )
        self.assertEqual(
            timesheet2.timesheet_invoice_type, 'non_billable_project',
            "Timesheets without task shoulbe be 'no project found'")
        self.assertFalse(timesheet1.timesheet_invoice_id,
                         "The timesheet1 should not be linked to the invoice")
        self.assertFalse(timesheet2.timesheet_invoice_id,
                         "The timesheet2 should not be linked to the invoice")

        # invoice SO
        sale_order.order_line.write({'qty_delivered': 5})
        invoice1 = sale_order._create_invoices()

        for invoice_line in invoice1.invoice_line_ids:
            self.assertEqual(
                invoice_line.quantity, 5,
                "The invoiced quantity should be 5, as manually set on SO lines"
            )

        self.assertFalse(
            timesheet1.timesheet_invoice_id,
            "The timesheet1 should not be linked to the invoice, since timesheets are used for time tracking in milestone"
        )
        self.assertFalse(
            timesheet2.timesheet_invoice_id,
            "The timesheet2 should not be linked to the invoice, since timesheets are used for time tracking in milestone"
        )

        # validate the invoice
        invoice1.action_post()

        self.assertFalse(
            timesheet1.timesheet_invoice_id,
            "The timesheet1 should not be linked to the invoice, even after invoice validation"
        )
        self.assertFalse(
            timesheet2.timesheet_invoice_id,
            "The timesheet2 should not be linked to the invoice, even after invoice validation"
        )
Exemplo n.º 36
0
 def bank_payment_predicate(args):
     return tools.float_is_zero(bank_payment.amount - args[0], precision_rounding=currency_rounding)
Exemplo n.º 37
0
    def _run_fifo_vacuum(self, company=None):
        """Compensate layer valued at an estimated price with the price of future receipts
        if any. If the estimated price is equals to the real price, no layer is created but
        the original layer is marked as compensated.

        :param company: recordset of `res.company` to limit the execution of the vacuum
        """
        self.ensure_one()
        if company is None:
            company = self.env.company
        svls_to_vacuum = self.env['stock.valuation.layer'].sudo().search(
            [
                ('product_id', '=', self.id),
                ('remaining_qty', '<', 0),
                ('stock_move_id', '!=', False),
                ('company_id', '=', company.id),
            ],
            order='create_date, id')
        as_svls = []
        for svl_to_vacuum in svls_to_vacuum:
            domain = [('company_id', '=', svl_to_vacuum.company_id.id),
                      ('product_id', '=', self.id),
                      ('remaining_qty', '>', 0), '|',
                      ('create_date', '>', svl_to_vacuum.create_date), '&',
                      ('create_date', '=', svl_to_vacuum.create_date),
                      ('id', '>', svl_to_vacuum.id)]
            candidates = self.env['stock.valuation.layer'].sudo().search(
                domain)
            if not candidates:
                break
            qty_to_take_on_candidates = abs(svl_to_vacuum.remaining_qty)
            qty_taken_on_candidates = 0
            tmp_value = 0
            for candidate in candidates:
                qty_taken_on_candidate = min(candidate.remaining_qty,
                                             qty_to_take_on_candidates)
                qty_taken_on_candidates += qty_taken_on_candidate

                candidate_unit_cost = candidate.remaining_value / candidate.remaining_qty
                value_taken_on_candidate = qty_taken_on_candidate * candidate_unit_cost
                value_taken_on_candidate = candidate.currency_id.round(
                    value_taken_on_candidate)
                new_remaining_value = candidate.remaining_value - value_taken_on_candidate

                candidate_vals = {
                    'remaining_qty':
                    candidate.remaining_qty - qty_taken_on_candidate,
                    'remaining_value': new_remaining_value
                }
                candidate.write(candidate_vals)

                qty_to_take_on_candidates -= qty_taken_on_candidate
                tmp_value += value_taken_on_candidate
                if float_is_zero(qty_to_take_on_candidates,
                                 precision_rounding=self.uom_id.rounding):
                    break

            # Get the estimated value we will correct.
            remaining_value_before_vacuum = svl_to_vacuum.unit_cost * qty_taken_on_candidates
            new_remaining_qty = svl_to_vacuum.remaining_qty + qty_taken_on_candidates
            corrected_value = remaining_value_before_vacuum - tmp_value
            svl_to_vacuum.write({
                'remaining_qty': new_remaining_qty,
            })

            # Don't create a layer or an accounting entry if the corrected value is zero.
            if svl_to_vacuum.currency_id.is_zero(corrected_value):
                continue

            corrected_value = svl_to_vacuum.currency_id.round(corrected_value)
            move = svl_to_vacuum.stock_move_id
            vals = {
                'product_id':
                self.id,
                'value':
                corrected_value,
                'unit_cost':
                0,
                'quantity':
                0,
                'remaining_qty':
                0,
                'stock_move_id':
                move.id,
                'company_id':
                move.company_id.id,
                'description':
                'Revaluation of %s (negative inventory)' % move.picking_id.name
                or move.name,
                'stock_valuation_layer_id':
                svl_to_vacuum.id,
            }
            vacuum_svl = self.env['stock.valuation.layer'].sudo().create(vals)

            # If some negative stock were fixed, we need to recompute the standard price.
            product = self.with_company(company.id)
            if product.cost_method == 'average' and not float_is_zero(
                    product.quantity_svl,
                    precision_rounding=self.uom_id.rounding):
                product.sudo().with_context(disable_auto_svl=True).write({
                    'standard_price':
                    product.value_svl / product.quantity_svl
                })

            if self.valuation != 'real_time':
                continue
            as_svls.append((vacuum_svl, svl_to_vacuum))

        self.env['stock.valuation.layer'].browse(
            x[0].id for x in as_svls)._validate_accounting_entries()

        for vacuum_svl, svl_to_vacuum in as_svls:
            self._create_fifo_vacuum_anglo_saxon_expense_entry(
                vacuum_svl, svl_to_vacuum)
Exemplo n.º 38
0
    def _apply_rules(self, st_lines, excluded_ids=None, partner_map=None):
        ''' Apply criteria to get candidates for all reconciliation models.
        :param st_lines:        Account.bank.statement.lines recordset.
        :param excluded_ids:    Account.move.lines to exclude.
        :param partner_map:     Dict mapping each line with new partner eventually.
        :return:                A dict mapping each statement line id with:
            * aml_ids:      A list of account.move.line ids.
            * model:        An account.reconcile.model record (optional).
            * status:       'reconciled' if the lines has been already reconciled, 'write_off' if the write-off must be
                            applied on the statement line.
        '''
        available_models = self.filtered(lambda m: m.rule_type != 'writeoff_button')

        results = dict((r.id, {'aml_ids': []}) for r in st_lines)

        if not available_models:
            return results

        ordered_models = available_models.sorted(key=lambda m: (m.sequence, m.id))

        grouped_candidates = {}

        # Type == 'invoice_matching'.
        # Map each (st_line.id, model_id) with matching amls.
        invoices_models = ordered_models.filtered(lambda m: m.rule_type == 'invoice_matching')
        if invoices_models:
            query, params = invoices_models._get_invoice_matching_query(st_lines, excluded_ids=excluded_ids, partner_map=partner_map)
            self._cr.execute(query, params)
            query_res = self._cr.dictfetchall()

            for res in query_res:
                grouped_candidates.setdefault(res['id'], {})
                grouped_candidates[res['id']].setdefault(res['model_id'], [])
                grouped_candidates[res['id']][res['model_id']].append(res)

        # Type == 'writeoff_suggestion'.
        # Map each (st_line.id, model_id) with a flag indicating the st_line matches the criteria.
        write_off_models = ordered_models.filtered(lambda m: m.rule_type == 'writeoff_suggestion')
        if write_off_models:
            query, params = write_off_models._get_writeoff_suggestion_query(st_lines, excluded_ids=excluded_ids, partner_map=partner_map)
            self._cr.execute(query, params)
            query_res = self._cr.dictfetchall()

            for res in query_res:
                grouped_candidates.setdefault(res['id'], {})
                grouped_candidates[res['id']].setdefault(res['model_id'], True)

        # Keep track of already processed amls.
        amls_ids_to_exclude = set()

        # Keep track of already reconciled amls.
        reconciled_amls_ids = set()

        # Iterate all and create results.
        for line in st_lines:
            line_currency = line.currency_id or line.journal_id.currency_id or line.company_id.currency_id
            line_residual = line.currency_id and line.amount_currency or line.amount

            # Search for applicable rule.
            # /!\ BREAK are very important here to avoid applying multiple rules on the same line.
            for model in ordered_models:
                # No result found.
                if not grouped_candidates.get(line.id) or not grouped_candidates[line.id].get(model.id):
                    continue

                excluded_lines_found = False

                if model.rule_type == 'invoice_matching':
                    candidates = grouped_candidates[line.id][model.id]

                    # If some invoices match on the communication, suggest them.
                    # Otherwise, suggest all invoices having the same partner.
                    # N.B: The only way to match a line without a partner is through the communication.
                    first_batch_candidates = []
                    second_batch_candidates = []
                    for c in candidates:
                        # Don't take into account already reconciled lines.
                        if c['aml_id'] in reconciled_amls_ids:
                            continue

                        # Dispatch candidates between lines matching invoices with the communication or only the partner.
                        if c['communication_flag']:
                            first_batch_candidates.append(c)
                        elif not first_batch_candidates:
                            second_batch_candidates.append(c)
                    available_candidates = first_batch_candidates or second_batch_candidates

                    # Special case: the amount are the same, submit the line directly.
                    for c in available_candidates:
                        residual_amount = c['aml_currency_id'] and c['aml_amount_residual_currency'] or c['aml_amount_residual']

                        if float_is_zero(residual_amount - line_residual, precision_rounding=line_currency.rounding):
                            available_candidates = [c]
                            break

                    # Needed to handle check on total residual amounts.
                    if first_batch_candidates or model._check_rule_propositions(line, available_candidates):
                        results[line.id]['model'] = model

                        # Add candidates to the result.
                        for candidate in available_candidates:

                            # Special case: the propositions match the rule but some of them are already consumed by
                            # another one. Then, suggest the remaining propositions to the user but don't make any
                            # automatic reconciliation.
                            if candidate['aml_id'] in amls_ids_to_exclude:
                                excluded_lines_found = True
                                continue

                            results[line.id]['aml_ids'].append(candidate['aml_id'])
                            amls_ids_to_exclude.add(candidate['aml_id'])

                        if excluded_lines_found:
                            break

                        # Create write-off lines.
                        move_lines = self.env['account.move.line'].browse(results[line.id]['aml_ids'])
                        partner = partner_map and partner_map.get(line.id) and self.env['res.partner'].browse(partner_map[line.id])
                        reconciliation_results = model._prepare_reconciliation(line, move_lines, partner=partner)

                        # A write-off must be applied.
                        if reconciliation_results['new_aml_dicts']:
                            results[line.id]['status'] = 'write_off'

                        # Process auto-reconciliation.
                        if model.auto_reconcile:
                            # An open balance is needed but no partner has been found.
                            if reconciliation_results['open_balance_dict'] is False:
                                break

                            new_aml_dicts = reconciliation_results['new_aml_dicts']
                            if reconciliation_results['open_balance_dict']:
                                new_aml_dicts.append(reconciliation_results['open_balance_dict'])
                            if not line.partner_id and partner:
                                line.partner_id = partner
                            counterpart_moves = line.process_reconciliation(
                                counterpart_aml_dicts=reconciliation_results['counterpart_aml_dicts'],
                                payment_aml_rec=reconciliation_results['payment_aml_rec'],
                                new_aml_dicts=new_aml_dicts,
                            )
                            results[line.id]['status'] = 'reconciled'
                            results[line.id]['reconciled_lines'] = counterpart_moves.mapped('line_ids')

                            # The reconciled move lines are no longer candidates for another rule.
                            reconciled_amls_ids.update(move_lines.ids)

                        # Break models loop.
                        break

                elif model.rule_type == 'writeoff_suggestion' and grouped_candidates[line.id][model.id]:
                    results[line.id]['model'] = model
                    results[line.id]['status'] = 'write_off'

                    # Create write-off lines.
                    partner = partner_map and partner_map.get(line.id) and self.env['res.partner'].browse(partner_map[line.id])
                    reconciliation_results = model._prepare_reconciliation(line, partner=partner)

                    # An open balance is needed but no partner has been found.
                    if reconciliation_results['open_balance_dict'] is False:
                        break

                    # Process auto-reconciliation.
                    if model.auto_reconcile:
                        new_aml_dicts = reconciliation_results['new_aml_dicts']
                        if reconciliation_results['open_balance_dict']:
                            new_aml_dicts.append(reconciliation_results['open_balance_dict'])
                        if not line.partner_id and partner:
                            line.partner_id = partner
                        counterpart_moves = line.process_reconciliation(
                            counterpart_aml_dicts=reconciliation_results['counterpart_aml_dicts'],
                            payment_aml_rec=reconciliation_results['payment_aml_rec'],
                            new_aml_dicts=new_aml_dicts,
                        )
                        results[line.id]['status'] = 'reconciled'
                        results[line.id]['reconciled_lines'] = counterpart_moves.mapped('line_ids')

                    # Break models loop.
                    break
        return results
Exemplo n.º 39
0
    def action_payslip_done(self):
        precision = self.env['decimal.precision'].precision_get('Payroll')

        for slip in self:
            line_ids = []
            debit_sum = 0.0
            credit_sum = 0.0
            date = slip.date or slip.date_to

            name = _('Payslip of %s') % (slip.employee_id.name)
            contract_analytic_account_id = None
            if slip.contract_id and slip.contract_id.analytic_account_id:
                contract_analytic_account_id = slip.contract_id.analytic_account_id.id
            move_dict = {
                'narration': name,
                'ref': slip.number,
                'journal_id': slip.journal_id.id,
                'date': date,
            }
            # for line in slip.line_ids: ?
            for line in slip.details_by_salary_rule_category:
                amount = slip.credit_note and -line.total or line.total
                if float_is_zero(amount, precision_digits=precision):
                    continue
                debit_account_id = line.salary_rule_id.account_debit.id
                credit_account_id = line.salary_rule_id.account_credit.id

                if debit_account_id:
                    debit_line = (0, 0, {
                        'name': line.name,
                        'partner_id': line._get_partner_id(credit_account=False),
                        'account_id': debit_account_id,
                        'journal_id': slip.journal_id.id,
                        'date': date,
                        'debit': amount > 0.0 and amount or 0.0,
                        'credit': amount < 0.0 and -amount or 0.0,
                        'analytic_account_id': line.salary_rule_id.analytic_account_id.id or contract_analytic_account_id,
                        'tax_line_id': line.salary_rule_id.account_tax_id.id,
                    })
                    line_ids.append(debit_line)
                    debit_sum += debit_line[2]['debit'] - debit_line[2]['credit']

                if credit_account_id:
                    credit_line = (0, 0, {
                        'name': line.name,
                        'partner_id': line._get_partner_id(credit_account=True),
                        'account_id': credit_account_id,
                        'journal_id': slip.journal_id.id,
                        'date': date,
                        'debit': amount < 0.0 and -amount or 0.0,
                        'credit': amount > 0.0 and amount or 0.0,
                        'analytic_account_id': line.salary_rule_id.analytic_account_id.id or contract_analytic_account_id,
                        'tax_line_id': line.salary_rule_id.account_tax_id.id,
                    })
                    line_ids.append(credit_line)
                    credit_sum += credit_line[2]['credit'] - credit_line[2]['debit']

            if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
                acc_id = slip.journal_id.default_account_id.id
                if not acc_id:
                    raise UserError(_('The Expense Journal "%s" has not properly configured the Default Account!') % (slip.journal_id.name))
                adjust_credit = (0, 0, {
                    'name': _('Adjustment Entry'),
                    'partner_id': False,
                    'account_id': acc_id,
                    'journal_id': slip.journal_id.id,
                    'date': date,
                    'debit': 0.0,
                    'credit': debit_sum - credit_sum,
                })
                line_ids.append(adjust_credit)

            elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
                acc_id = slip.journal_id.default_account_id.id
                if not acc_id:
                    raise UserError(_('The Expense Journal "%s" has not properly configured the Default Account!') % (slip.journal_id.name))
                adjust_debit = (0, 0, {
                    'name': _('Adjustment Entry'),
                    'partner_id': False,
                    'account_id': acc_id,
                    'journal_id': slip.journal_id.id,
                    'date': date,
                    'debit': credit_sum - debit_sum,
                    'credit': 0.0,
                })
                line_ids.append(adjust_debit)
            move_dict['line_ids'] = line_ids
            move = self.env['account.move'].create(move_dict)
            slip.write({'move_id': move.id, 'date': date})
            move.post()

        return self.write({'state': 'done'})
Exemplo n.º 40
0
    def _process_order(self, pos_order):
        prec_acc = self.env['decimal.precision'].precision_get('Account')
        pos_session = self.env['pos.session'].browse(
            pos_order['pos_session_id'])
        if pos_session.state == 'closing_control' or pos_session.state == 'closed':
            pos_order['pos_session_id'] = self._get_valid_session(pos_order).id
        if pos_order.get('is_return_order', False):
            pos_order['amount_paid'] = 0
            for line in pos_order['lines']:
                line_dict = line[2]
                line_dict['qty'] = line_dict['qty'] * -1
                original_line = self.env['pos.order.line'].browse(
                    line_dict.get('original_line_id', False))
                original_line.line_qty_returned += abs(line_dict.get('qty', 0))
            for statement in pos_order['statement_ids']:
                statement_dict = statement[2]
                statement_dict['amount'] = statement_dict['amount'] * -1
            pos_order['amount_tax'] = pos_order['amount_tax'] * -1
            pos_order['amount_return'] = 0
            pos_order['amount_total'] = pos_order['amount_total'] * -1

        order = self.create(self._order_fields(pos_order))
        journal_ids = set()
        for payments in pos_order['statement_ids']:
            if not float_is_zero(payments[2]['amount'],
                                 precision_digits=prec_acc):
                order.add_payment(self._payment_fields(payments[2]))
            journal_ids.add(payments[2]['journal_id'])

        if pos_session.sequence_number <= pos_order['sequence_number']:
            pos_session.write(
                {'sequence_number': pos_order['sequence_number'] + 1})
            pos_session.refresh()

        if not float_is_zero(pos_order['amount_return'], prec_acc):
            cash_journal_id = pos_session.cash_journal_id.id
            if not cash_journal_id:
                # Select for change one of the cash journals used in this
                # payment
                cash_journal = self.env['account.journal'].search([
                    ('type', '=', 'cash'),
                    ('id', 'in', list(journal_ids)),
                ],
                                                                  limit=1)
                if not cash_journal:
                    # If none, select for change one of the cash journals of the POS
                    # This is used for example when a customer pays by credit card
                    # an amount higher than total amount of the order and gets
                    # cash back
                    cash_journal = [
                        statement.journal_id
                        for statement in pos_session.statement_ids
                        if statement.journal_id.type == 'cash'
                    ]
                    if not cash_journal:
                        raise UserError(
                            _("No cash statement found for this session. Unable to record returned cash."
                              ))
                cash_journal_id = cash_journal[0].id
            order.add_payment({
                'amount': -pos_order['amount_return'],
                'payment_date': fields.Datetime.now(),
                'payment_name': _('return'),
                'journal': cash_journal_id,
            })
        return order
Exemplo n.º 41
0
 def statement_line_predicate(args):
     return tools.float_is_zero(statement_line.amount - args[0], precision_rounding=currency_rounding)
Exemplo n.º 42
0
 def _is_difference_zero(self):
     for bank_stmt in self:
         bank_stmt.is_difference_zero = float_is_zero(
             bank_stmt.difference,
             precision_digits=bank_stmt.currency_id.decimal_places)
    def get_lines(self, financial_report, context, currency_table, linesDicts):
        final_result_table = []
        comparison_table = context.get_periods()
        currency_precision = self.env.user.company_id.currency_id.rounding
        # build comparison table

        for line in self:
            res = []
            debit_credit = len(comparison_table) == 1
            domain_ids = {'line'}
            k = 0
            for period in comparison_table:
                period_from = period[0]
                period_to = period[1]
                strict_range = False
                if line.special_date_changer == 'from_beginning':
                    period_from = False
                if line.special_date_changer == 'to_beginning_of_period':
                    date_tmp = datetime.strptime(
                        period[0], "%Y-%m-%d") - relativedelta(days=1)
                    period_to = date_tmp.strftime('%Y-%m-%d')
                    period_from = False
                if line.special_date_changer == 'strict_range':
                    strict_range = True
                r = line.with_context(date_from=period_from,
                                      date_to=period_to,
                                      strict_range=strict_range)._eval_formula(
                                          financial_report, debit_credit,
                                          context, currency_table,
                                          linesDicts[k])
                debit_credit = False
                res.append(r)
                domain_ids.update(set(r.keys()))
                k += 1
            res = self._put_columns_together(res, domain_ids)
            if line.hide_if_zero and all([
                    float_is_zero(k, precision_rounding=currency_precision)
                    for k in res['line']
            ]):
                continue

            # Post-processing ; creating line dictionnary, building comparison, computing total for extended, formatting
            vals = {
                'id':
                line.id,
                'name':
                line.name,
                'type':
                'line',
                'level':
                line.level,
                'footnotes':
                context._get_footnotes('line', line.id),
                'columns':
                res['line'],
                'unfoldable':
                len(domain_ids) > 1 and line.show_domain != 'always',
                'unfolded':
                line in context.unfolded_lines or line.show_domain == 'always',
            }
            if line.action_id:
                vals['action_id'] = line.action_id.id
            domain_ids.remove('line')
            lines = [vals]
            groupby = line.groupby or 'aml'
            if line in context.unfolded_lines or line.show_domain == 'always':
                if line.groupby:
                    domain_ids = sorted(list(domain_ids),
                                        key=lambda k: line._get_gb_name(k))
                for domain_id in domain_ids:
                    name = line._get_gb_name(domain_id)
                    vals = {
                        'id': domain_id,
                        'name': name and len(name) >= 45 and name[0:40] + '...'
                        or name,
                        'level': 1,
                        'type': groupby,
                        'footnotes':
                        context._get_footnotes(groupby, domain_id),
                        'columns': res[domain_id],
                    }
                    if line.financial_report_id.name == 'Aged Receivable':
                        vals['trust'] = self.env['res.partner'].browse(
                            [domain_id]).trust
                    lines.append(vals)
                if domain_ids:
                    lines.append({
                        'id':
                        line.id,
                        'name':
                        _('Total') + ' ' + line.name,
                        'type':
                        'o_account_reports_domain_total',
                        'level':
                        1,
                        'footnotes':
                        context._get_footnotes(
                            'o_account_reports_domain_total', line.id),
                        'columns':
                        list(lines[0]['columns']),
                    })

            for vals in lines:
                if len(comparison_table) == 2:
                    vals['columns'].append(
                        line._build_cmp(vals['columns'][0],
                                        vals['columns'][1]))
                    for i in [0, 1]:
                        vals['columns'][i] = line._format(vals['columns'][i])
                else:
                    vals['columns'] = map(line._format, vals['columns'])
                if not line.formulas:
                    vals['columns'] = ['' for k in vals['columns']]

            if len(lines) == 1:
                new_lines = line.children_ids.get_lines(
                    financial_report, context, currency_table, linesDicts)
                if new_lines and line.level > 0 and line.formulas:
                    divided_lines = self._divide_line(lines[0])
                    result = [divided_lines[0]
                              ] + new_lines + [divided_lines[1]]
                else:
                    result = []
                    if line.level > 0:
                        result += lines
                    result += new_lines
                    if line.level <= 0:
                        result += lines
            else:
                result = lines
            final_result_table += result

        return final_result_table
Exemplo n.º 44
0
    def _process_order(self, order):
        # Don't change original dict, because in case of SERIALIZATION_FAILURE
        # the method will be called again
        order = copy.deepcopy(order)
        postponed_payments = False
        account_precision = self.env['decimal.precision'].precision_get(
            'Account')
        # ignore orders with an amount_paid of 0 because those are returns through the POS
        if not float_is_zero(order['amount_paid'], account_precision):
            acc_journal = self.env['account.journal']
            payments = order.get('statement_ids')
            postponed_payments = filter(
                lambda x: acc_journal.browse(x[2]['journal_id']).
                postponed_invoice, payments)
            if postponed_payments:
                user_id = self.env['res.users'].browse(order['user_id'])
                partner_id = self.env['res.partner'].browse(
                    order['partner_id'])
                session_id = self.env['pos.session'].browse(
                    order['pos_session_id'])
                # pricelist_id = self.env['res.priselist'].browse(order['pricelist_id'])
                invoice = self.env['account.invoice'].sudo().create({
                    'name':
                    order['name'],
                    'origin':
                    order['name'],
                    'account_id':
                    partner_id.property_account_receivable_id.id,
                    'journal_id':
                    session_id.config_id.invoice_journal_id.id,
                    'company_id':
                    user_id.company_id.id,
                    'type':
                    'out_invoice',
                    'reference':
                    order['name'],
                    'partner_id':
                    partner_id.id,
                    'comment':
                    'note' in order and order['note'] or '',
                    # considering partner's sale pricelist's currency
                    # 'currency_id': pricelist_id.currency_id.id,
                    'user_id':
                    user_id.id,
                })
                order['invoice_id'] = invoice.id
                order['statement_ids'] = [
                    p for p in payments if p not in postponed_payments
                ]
                order['amount_paid'] = order['amount_paid'] - sum(
                    [p[2]['amount'] for p in postponed_payments] + [0])
                order['amount_return'] = max(
                    order['amount_paid'] - order['amount_total'], 0)

        res = super(PosOrder, self)._process_order(order)

        if postponed_payments:
            res.sudo().write({
                'state': 'invoiced',
                'invoice_id': invoice.id,
                'postponed': True,
            })
            invoice._onchange_partner_id()
            invoice.fiscal_position_id = res.fiscal_position_id
            message = _(
                "This Postponed invoice has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>"
            ) % (res.id, res.name)
            invoice.message_post(body=message)
            for line in res.lines:
                self._action_create_invoice_line(line, invoice.id)
            invoice.sudo().compute_taxes()
            invoice.sudo().action_invoice_open()

        return res
Exemplo n.º 45
0
    def _get_invoiced_lot_values(self):
        """ Get and prepare data to show a table of invoiced lot on the invoice's report. """
        self.ensure_one()

        if self.state == 'draft':
            return []

        sale_orders = self.mapped('invoice_line_ids.sale_line_ids.order_id')
        stock_move_lines = sale_orders.mapped(
            'picking_ids.move_lines.move_line_ids')

        # Get the other customer invoices and refunds.
        ordered_invoice_ids = sale_orders.mapped('invoice_ids')\
            .filtered(lambda i: i.state not in ['draft', 'cancel'])\
            .sorted(lambda i: (i.invoice_date, i.id))

        # Get the position of self in other customer invoices and refunds.
        self_index = None
        i = 0
        for invoice in ordered_invoice_ids:
            if invoice.id == self.id:
                self_index = i
                break
            i += 1

        # Get the previous invoice if any.
        previous_invoices = ordered_invoice_ids[:self_index]
        last_invoice = previous_invoices[-1] if len(
            previous_invoices) else None

        # Get the incoming and outgoing sml between self.invoice_date and the previous invoice (if any).
        self_datetime = max(self.invoice_line_ids.mapped(
            'write_date')) if self.invoice_line_ids else None
        last_invoice_datetime = max(
            last_invoice.invoice_line_ids.mapped(
                'write_date')) if last_invoice else None

        def _filter_incoming_sml(ml):
            if ml.state == 'done' and ml.location_id.usage == 'customer' and ml.lot_id:
                if last_invoice_datetime:
                    return last_invoice_datetime <= ml.date <= self_datetime
                else:
                    return ml.date <= self_datetime
            return False

        def _filter_outgoing_sml(ml):
            if ml.state == 'done' and ml.location_dest_id.usage == 'customer' and ml.lot_id:
                if last_invoice_datetime:
                    return last_invoice_datetime <= ml.date <= self_datetime
                else:
                    return ml.date <= self_datetime
            return False

        incoming_sml = stock_move_lines.filtered(_filter_incoming_sml)
        outgoing_sml = stock_move_lines.filtered(_filter_outgoing_sml)

        # Prepare and return lot_values
        qties_per_lot = defaultdict(lambda: 0)
        if self.move_type == 'out_refund':
            for ml in outgoing_sml:
                qties_per_lot[
                    ml.lot_id] -= ml.product_uom_id._compute_quantity(
                        ml.qty_done, ml.product_id.uom_id)
            for ml in incoming_sml:
                qties_per_lot[
                    ml.lot_id] += ml.product_uom_id._compute_quantity(
                        ml.qty_done, ml.product_id.uom_id)
        else:
            for ml in outgoing_sml:
                qties_per_lot[
                    ml.lot_id] += ml.product_uom_id._compute_quantity(
                        ml.qty_done, ml.product_id.uom_id)
            for ml in incoming_sml:
                qties_per_lot[
                    ml.lot_id] -= ml.product_uom_id._compute_quantity(
                        ml.qty_done, ml.product_id.uom_id)
        lot_values = []
        for lot_id, qty in qties_per_lot.items():
            if float_is_zero(
                    qty, precision_rounding=lot_id.product_id.uom_id.rounding):
                continue
            lot_values.append({
                'product_name': lot_id.product_id.display_name,
                'quantity': qty,
                'uom_name': lot_id.product_uom_id.name,
                'lot_name': lot_id.name,
            })
        return lot_values
Exemplo n.º 46
0
    def _get_write_off_move_lines_dict(self, st_line, move_lines=None):
        ''' Get move.lines dict (to be passed to the create()) corresponding to the reconciliation model's write-off lines.
        :param st_line:     An account.bank.statement.line record.
        :param move_lines:  An account.move.line recordset.
        :return: A list of dict representing move.lines to be created corresponding to the write-off lines.
        '''
        self.ensure_one()

        if self.rule_type == 'invoice_matching' and (not self.match_total_amount or (self.match_total_amount_param == 100)):
            return []

        line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
        line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
        total_residual = move_lines and sum(aml.currency_id and aml.amount_residual_currency or aml.amount_residual for aml in move_lines) or 0.0

        balance = total_residual - line_residual

        if not self.account_id or float_is_zero(balance, precision_rounding=line_currency.rounding):
            return []

        if self.amount_type == 'percentage':
            line_balance = balance * (self.amount / 100.0)
        else:
            line_balance = self.amount * (1 if balance > 0.0 else -1)

        new_aml_dicts = []

        # First write-off line.
        writeoff_line = {
            'name': self.label or st_line.name,
            'account_id': self.account_id.id,
            'analytic_account_id': self.analytic_account_id.id,
            'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
            'debit': line_balance > 0 and line_balance or 0,
            'credit': line_balance < 0 and -line_balance or 0,
        }
        new_aml_dicts.append(writeoff_line)

        if self.tax_ids:
            writeoff_line['tax_ids'] = [(6, None, self.tax_ids.ids)]
            tax = self.tax_ids
            # Multiple taxes with force_tax_included results in wrong computation, so we
            # only allow to set the force_tax_included field if we have one tax selected
            if self.force_tax_included:
                tax = tax[0].with_context(force_price_include=True)
            new_aml_dicts += self._get_taxes_move_lines_dict(tax, writeoff_line)

        # Second write-off line.
        if self.has_second_line and self.second_account_id:
            line_balance = balance - sum(aml['debit'] - aml['credit'] for aml in new_aml_dicts)
            second_writeoff_line = {
                'name': self.second_label or st_line.name,
                'account_id': self.second_account_id.id,
                'analytic_account_id': self.second_analytic_account_id.id,
                'analytic_tag_ids': [(6, 0, self.second_analytic_tag_ids.ids)],
                'debit': line_balance > 0 and line_balance or 0,
                'credit': line_balance < 0 and -line_balance or 0,
            }
            new_aml_dicts.append(second_writeoff_line)

            if self.second_tax_ids:
                second_writeoff_line['tax_ids'] = [(6, None, self.second_tax_ids.ids)]
                tax = self.second_tax_ids
                # Multiple taxes with force_tax_included results in wrong computation, so we
                # only allow to set the force_tax_included field if we have one tax selected
                if self.force_second_tax_included:
                    tax = tax[0].with_context(force_price_include=True)
                new_aml_dicts += self._get_taxes_move_lines_dict(tax, second_writeoff_line)

        return new_aml_dicts
Exemplo n.º 47
0
    def compute_depreciation_board(self):
        self.ensure_one()

        posted_depreciation_line_ids = self.depreciation_line_ids.filtered(
            lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date)
        unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(
            lambda x: not x.move_check)

        # Remove old unposted depreciation lines. We cannot use unlink() with One2many field
        commands = [(2, line_id.id, False)
                    for line_id in unposted_depreciation_line_ids]

        if self.value_residual != 0.0:
            amount_to_depr = residual_amount = self.value_residual
            if self.prorata:
                # if we already have some previous validated entries, starting date is last entry + method perio
                if posted_depreciation_line_ids and posted_depreciation_line_ids[
                        -1].depreciation_date:
                    last_depreciation_date = datetime.strptime(
                        posted_depreciation_line_ids[-1].depreciation_date,
                        DF).date()
                    depreciation_date = last_depreciation_date + relativedelta(
                        months=+self.method_period)
                else:
                    depreciation_date = datetime.strptime(
                        self._get_last_depreciation_date()[self.id],
                        DF).date()
            else:
                # depreciation_date = 1st of January of purchase year if annual valuation, 1st of
                # purchase month in other cases
                if self.method_period >= 12:
                    if self.company_id.fiscalyear_last_month:
                        asset_date = date(year=int(self.date[:4]),
                                                   month=self.company_id.fiscalyear_last_month,
                                                   day=self.company_id.fiscalyear_last_day) + \
                                     relativedelta(days=1) + \
                                     relativedelta(year=int(self.date[:4]))  # e.g. 2018-12-31 +1 -> 2019
                    else:
                        asset_date = datetime.strptime(
                            self.date[:4] + '-01-01', DF).date()
                else:
                    asset_date = datetime.strptime(self.date[:7] + '-01',
                                                   DF).date()
                # if we already have some previous validated entries, starting date isn't 1st January but last entry + method period
                if posted_depreciation_line_ids and posted_depreciation_line_ids[
                        -1].depreciation_date:
                    last_depreciation_date = datetime.strptime(
                        posted_depreciation_line_ids[-1].depreciation_date,
                        DF).date()
                    depreciation_date = last_depreciation_date + relativedelta(
                        months=+self.method_period)
                else:
                    depreciation_date = asset_date
            day = depreciation_date.day
            month = depreciation_date.month
            year = depreciation_date.year
            total_days = (year % 4) and 365 or 366

            undone_dotation_number = self._compute_board_undone_dotation_nb(
                depreciation_date, total_days)

            for x in range(len(posted_depreciation_line_ids),
                           undone_dotation_number):
                sequence = x + 1
                amount = self._compute_board_amount(
                    sequence, residual_amount, amount_to_depr,
                    undone_dotation_number, posted_depreciation_line_ids,
                    total_days, depreciation_date)
                amount = self.currency_id.round(amount)
                if float_is_zero(amount,
                                 precision_rounding=self.currency_id.rounding):
                    continue
                residual_amount -= amount
                vals = {
                    'amount':
                    amount,
                    'asset_id':
                    self.id,
                    'sequence':
                    sequence,
                    'name': (self.code or '') + '/' + str(sequence),
                    'remaining_value':
                    residual_amount,
                    'depreciated_value':
                    self.value - (self.salvage_value + residual_amount),
                    'depreciation_date':
                    depreciation_date.strftime(DF),
                }
                commands.append((0, False, vals))
                # Considering Depr. Period as months
                depreciation_date = date(year, month, day) + relativedelta(
                    months=+self.method_period)
                day = depreciation_date.day
                month = depreciation_date.month
                year = depreciation_date.year

        self.write({'depreciation_line_ids': commands})

        return True
Exemplo n.º 48
0
    def _change_standard_price(self, new_price, counterpart_account_id=False):
        """Helper to create the stock valuation layers and the account moves
        after an update of standard price.

        :param new_price: new standard price
        """
        # Handle stock valuation layers.
        svl_vals_list = []
        company_id = self.env.company
        for product in self:
            if product.cost_method not in ('standard', 'average'):
                continue
            quantity_svl = product.sudo().quantity_svl
            if float_is_zero(quantity_svl,
                             precision_rounding=product.uom_id.rounding):
                continue
            diff = new_price - product.standard_price
            value = company_id.currency_id.round(quantity_svl * diff)
            if company_id.currency_id.is_zero(value):
                continue

            svl_vals = {
                'company_id':
                company_id.id,
                'product_id':
                product.id,
                'description':
                _('Product value manually modified (from %s to %s)') %
                (product.standard_price, new_price),
                'value':
                value,
                'quantity':
                0,
            }
            svl_vals_list.append(svl_vals)
        stock_valuation_layers = self.env['stock.valuation.layer'].sudo(
        ).create(svl_vals_list)

        # Handle account moves.
        product_accounts = {
            product.id: product.product_tmpl_id.get_product_accounts()
            for product in self
        }
        am_vals_list = []
        for stock_valuation_layer in stock_valuation_layers:
            product = stock_valuation_layer.product_id
            value = stock_valuation_layer.value

            if product.valuation != 'real_time':
                continue

            # Sanity check.
            if counterpart_account_id is False:
                raise UserError(_('You must set a counterpart account.'))
            if not product_accounts[product.id].get('stock_valuation'):
                raise UserError(
                    _('You don\'t have any stock valuation account defined on your product category. You must define one before processing this operation.'
                      ))

            if value < 0:
                debit_account_id = counterpart_account_id
                credit_account_id = product_accounts[
                    product.id]['stock_valuation'].id
            else:
                debit_account_id = product_accounts[
                    product.id]['stock_valuation'].id
                credit_account_id = counterpart_account_id

            move_vals = {
                'journal_id':
                product_accounts[product.id]['stock_journal'].id,
                'company_id':
                company_id.id,
                'ref':
                product.default_code,
                'stock_valuation_layer_ids':
                [(6, None, [stock_valuation_layer.id])],
                'line_ids': [(0, 0, {
                    'name':
                    _('%s changed cost from %s to %s - %s') %
                    (self.env.user.name, product.standard_price, new_price,
                     product.display_name),
                    'account_id':
                    debit_account_id,
                    'debit':
                    abs(value),
                    'credit':
                    0,
                    'product_id':
                    product.id,
                }),
                             (0, 0, {
                                 'name':
                                 _('%s changed cost from %s to %s - %s') %
                                 (self.env.user.name, product.standard_price,
                                  new_price, product.display_name),
                                 'account_id':
                                 credit_account_id,
                                 'debit':
                                 0,
                                 'credit':
                                 abs(value),
                                 'product_id':
                                 product.id,
                             })],
            }
            am_vals_list.append(move_vals)
        account_moves = self.env['account.move'].create(am_vals_list)
        account_moves.post()

        # Actually update the standard price.
        self.with_context(force_company=company_id.id).sudo().write(
            {'standard_price': new_price})
Exemplo n.º 49
0
    def _group_by_account_id(self, options, line_id):
        accounts = {}
        results = self._do_query_group_by_account(options, line_id)
        initial_bal_date_to = fields.Date.from_string(
            self.env.context['date_from_aml']) + timedelta(days=-1)
        initial_bal_results = self.with_context(
            date_to=initial_bal_date_to.strftime(
                '%Y-%m-%d'))._do_query_group_by_account(options, line_id)

        context = self.env.context

        last_day_previous_fy = self.env.user.company_id.compute_fiscalyear_dates(
            fields.Date.from_string(
                self.env.context['date_from_aml']))['date_from'] + timedelta(
                    days=-1)
        unaffected_earnings_per_company = {}
        for cid in context.get('company_ids', []):
            company = self.env['res.company'].browse(cid)
            unaffected_earnings_per_company[company] = self.with_context(
                date_to=last_day_previous_fy.strftime('%Y-%m-%d'),
                date_from=False)._do_query_unaffected_earnings(
                    options, line_id, company)

        unaff_earnings_treated_companies = set()
        unaffected_earnings_type = self.env.ref(
            'account.data_unaffected_earnings')
        for account_id, result in results.items():
            account = self.env['account.account'].browse(account_id)
            accounts[account] = result
            accounts[account]['initial_bal'] = initial_bal_results.get(
                account.id, {
                    'balance': 0,
                    'amount_currency': 0,
                    'debit': 0,
                    'credit': 0
                })
            if account.user_type_id == unaffected_earnings_type and account.company_id not in unaff_earnings_treated_companies:
                #add the benefit/loss of previous fiscal year to unaffected earnings accounts
                unaffected_earnings_results = unaffected_earnings_per_company[
                    account.company_id]
                for field in ['balance', 'debit', 'credit']:
                    accounts[account]['initial_bal'][
                        field] += unaffected_earnings_results[field]
                    accounts[account][field] += unaffected_earnings_results[
                        field]
                unaff_earnings_treated_companies.add(account.company_id)
            #use query_get + with statement instead of a search in order to work in cash basis too
            aml_ctx = {}
            if context.get('date_from_aml'):
                aml_ctx = {
                    'strict_range': True,
                    'date_from': context['date_from_aml'],
                }
            aml_ids = self.with_context(**aml_ctx)._do_query(
                options, account_id, group_by_account=False)
            aml_ids = [x[0] for x in aml_ids]

            accounts[account]['total_lines'] = len(aml_ids)
            offset = int(options.get('lines_offset', 0))
            if self.MAX_LINES:
                stop = offset + self.MAX_LINES
            else:
                stop = None
            if not context.get('print_mode'):
                aml_ids = aml_ids[offset:stop]

            accounts[account]['lines'] = self.env['account.move.line'].browse(
                aml_ids)

        # For each company, if the unaffected earnings account wasn't in the selection yet: add it manually
        user_currency = self.env.user.company_id.currency_id
        for cid in context.get('company_ids', []):
            company = self.env['res.company'].browse(cid)
            if company not in unaff_earnings_treated_companies and not float_is_zero(
                    unaffected_earnings_per_company[company]['balance'],
                    precision_digits=user_currency.decimal_places):
                unaffected_earnings_account = self.env[
                    'account.account'].search(
                        [('user_type_id', '=', unaffected_earnings_type.id),
                         ('company_id', '=', company.id)],
                        limit=1)
                if unaffected_earnings_account and (
                        not line_id
                        or unaffected_earnings_account.id == line_id):
                    accounts[unaffected_earnings_account[
                        0]] = unaffected_earnings_per_company[company]
                    accounts[unaffected_earnings_account[0]][
                        'initial_bal'] = unaffected_earnings_per_company[
                            company]
                    accounts[unaffected_earnings_account[0]]['lines'] = []
                    accounts[unaffected_earnings_account[0]]['total_lines'] = 0
        return accounts
Exemplo n.º 50
0
    def _change_standard_price(self, new_price):
        """Helper to create the stock valuation layers and the account moves
        after an update of standard price.

        :param new_price: new standard price
        """
        # Handle stock valuation layers.

        if self.filtered(
                lambda p: p.valuation == 'real_time'
        ) and not self.env['stock.valuation.layer'].check_access_rights(
                'read', raise_exception=False):
            raise UserError(
                _("You cannot update the cost of a product in automated valuation as it leads to the creation of a journal entry, for which you don't have the access rights."
                  ))

        svl_vals_list = []
        company_id = self.env.company
        for product in self:
            if product.cost_method not in ('standard', 'average'):
                continue
            quantity_svl = product.sudo().quantity_svl
            if float_is_zero(quantity_svl,
                             precision_rounding=product.uom_id.rounding):
                continue
            diff = new_price - product.standard_price
            value = company_id.currency_id.round(quantity_svl * diff)
            if company_id.currency_id.is_zero(value):
                continue

            svl_vals = {
                'company_id':
                company_id.id,
                'product_id':
                product.id,
                'description':
                _('Product value manually modified (from %s to %s)') %
                (product.standard_price, new_price),
                'value':
                value,
                'quantity':
                0,
            }
            svl_vals_list.append(svl_vals)
        stock_valuation_layers = self.env['stock.valuation.layer'].sudo(
        ).create(svl_vals_list)

        # Handle account moves.
        product_accounts = {
            product.id: product.product_tmpl_id.get_product_accounts()
            for product in self
        }
        am_vals_list = []
        for stock_valuation_layer in stock_valuation_layers:
            product = stock_valuation_layer.product_id
            value = stock_valuation_layer.value

            if product.valuation != 'real_time':
                continue

            # Sanity check.
            if not product_accounts[product.id].get('expense'):
                raise UserError(
                    _('You must set a counterpart account on your product category.'
                      ))
            if not product_accounts[product.id].get('stock_valuation'):
                raise UserError(
                    _('You don\'t have any stock valuation account defined on your product category. You must define one before processing this operation.'
                      ))

            if value < 0:
                debit_account_id = product_accounts[product.id]['expense'].id
                credit_account_id = product_accounts[
                    product.id]['stock_valuation'].id
            else:
                debit_account_id = product_accounts[
                    product.id]['stock_valuation'].id
                credit_account_id = product_accounts[product.id]['expense'].id

            move_vals = {
                'journal_id':
                product_accounts[product.id]['stock_journal'].id,
                'company_id':
                company_id.id,
                'ref':
                product.default_code,
                'stock_valuation_layer_ids':
                [(6, None, [stock_valuation_layer.id])],
                'move_type':
                'entry',
                'line_ids':
                [(0, 0,
                  {
                      'name':
                      _('%(user)s changed cost from %(previous)s to %(new_price)s - %(product)s',
                        user=self.env.user.name,
                        previous=product.standard_price,
                        new_price=new_price,
                        product=product.display_name),
                      'account_id':
                      debit_account_id,
                      'debit':
                      abs(value),
                      'credit':
                      0,
                      'product_id':
                      product.id,
                  }),
                 (0, 0, {
                     'name':
                     _('%(user)s changed cost from %(previous)s to %(new_price)s - %(product)s',
                       user=self.env.user.name,
                       previous=product.standard_price,
                       new_price=new_price,
                       product=product.display_name),
                     'account_id':
                     credit_account_id,
                     'debit':
                     0,
                     'credit':
                     abs(value),
                     'product_id':
                     product.id,
                 })],
            }
            am_vals_list.append(move_vals)

        account_moves = self.env['account.move'].sudo().create(am_vals_list)
        if account_moves:
            account_moves._post()
Exemplo n.º 51
0
 def predicate(args):
     payment_method, amount = args
     first = payment_method == pos_payment.payment_method_id
     second = tools.float_is_zero(pos_payment.amount - amount, precision_rounding=currency_rounding)
     return first and second
Exemplo n.º 52
0
    def pp_update_history_price_locations(self):
        self.ensure_one()
        ml_data = self.env['stock.move.line'].read_group(
            [('product_id', '=', self.id)],
            ['product_id', 'lot_id', 'package_id'],
            ['product_id', 'lot_id', 'package_id'],
            lazy=False)
        if_move_line = False
        get_param = self.env['ir.config_parameter'].sudo().get_param
        tax_included = get_param('bo_stock_card.tax_included')

        if self.last_location_dict:
            loc = literal_eval(self.last_location_dict)
            base_loc = loc.copy()

            for data in ml_data:
                move_lines = self.env['stock.move.line'].search(
                    expression.AND([
                        data['__domain'],
                        [('date', '>', loc.get('move_line_last_datetime')),
                         ('state', '=', 'done')]
                    ]),
                    order='date asc')
                sorted_move_lines = sorted(move_lines,
                                           key=lambda a: a.set_date or a.date)
                # Verify not key add in dict loc
                loc.update({(sml.location_dest_id.id, sml.product_id.id,
                             sml.lot_id.id, sml.package_id.id): {
                                 'product_tot_qty_available': 0.0,
                                 'cost': 0.0,
                                 'product_id': sml.product_id.id,
                                 'usage': sml.location_dest_id.usage,
                                 'lot_id': sml.lot_id.id,
                                 'package_id': sml.package_id.id
                             }
                            for sml in sorted_move_lines
                            if (sml.location_dest_id.id, sml.product_id.id,
                                sml.lot_id.id, sml.package_id.id) not in loc})

                loc.update({(sml.location_id.id, sml.product_id.id,
                             sml.lot_id.id, sml.package_id.id): {
                                 'product_tot_qty_available': 0.0,
                                 'cost': 0.0,
                                 'product_id': sml.product_id.id,
                                 'usage': sml.location_id.usage,
                                 'lot_id': sml.lot_id.id,
                                 'package_id': sml.package_id.id
                             }
                            for sml in sorted_move_lines
                            if (sml.location_id.id, sml.product_id.id,
                                sml.lot_id.id, sml.package_id.id) not in loc})
                for index, ml in enumerate(sorted_move_lines):
                    qty_done = ml.qty_done
                    if tax_included == 'True':
                        price_unit = abs(ml.move_id._get_price_unit_bo())
                    else:
                        price_unit = abs(ml.move_id.price_unit)
                    rounding = ml.product_id.uom_id.rounding
                    if not index and (ml.location_dest_id.id, ml.product_id.id,
                                      ml.lot_id.id,
                                      ml.package_id.id) not in base_loc:
                        loc.get(
                            (ml.location_dest_id.id, ml.product_id.id,
                             ml.lot_id.id,
                             ml.package_id.id)).update({'cost': price_unit})
                        price_used = ml.product_id.get_history_price(
                            self.env.user.company_id.id,
                            date=ml.set_date or ml.date,
                        )
                        # Save price_standar
                        self.env['product.price.history.card'].create({
                            'datetime':
                            ml.set_date or ml.date,
                            'cost':
                            loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')
                            or price_used,
                            'location_id':
                            ml.location_dest_id.id,
                            'product_id':
                            ml.product_id.id,
                            'lot_id':
                            ml.lot_id.id,
                            'package_id':
                            ml.package_id.id
                        })
                    else:
                        if ml.location_id.usage not in [
                                'internal', 'inventory'
                        ] and ml.location_dest_id.usage == 'internal':
                            product_tot_qty_available = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id
                                 )).get('product_tot_qty_available')
                            new_std_price = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')
                            if float_is_zero(product_tot_qty_available,
                                             precision_rounding=rounding):
                                new_std_price = price_unit
                            elif float_is_zero(product_tot_qty_available +
                                               qty_done,
                                               precision_rounding=rounding):
                                new_std_price = price_unit
                            else:
                                new_std_price = (
                                    (new_std_price * product_tot_qty_available)
                                    + (price_unit * qty_done)) / (
                                        product_tot_qty_available + qty_done)
                            loc.get((ml.location_dest_id.id, ml.product_id.id,
                                     ml.lot_id.id, ml.package_id.id)).update(
                                         {'cost': new_std_price})
                            # Save price_standar
                            self.env['product.price.history.card'].create({
                                'datetime':
                                ml.set_date or ml.date,
                                'cost':
                                loc.get((ml.location_dest_id.id,
                                         ml.product_id.id, ml.lot_id.id,
                                         ml.package_id.id)).get('cost'),
                                'location_id':
                                ml.location_dest_id.id,
                                'product_id':
                                ml.product_id.id,
                                'lot_id':
                                ml.lot_id.id,
                                'package_id':
                                ml.package_id.id
                            })

                        if ml.location_id.usage == 'internal' and ml.location_dest_id.usage == 'internal':
                            product_tot_qty_available = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id
                                 )).get('product_tot_qty_available')
                            price_unit = loc.get(
                                (ml.location_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')

                            new_std_price = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')
                            if float_is_zero(product_tot_qty_available,
                                             precision_rounding=rounding):
                                new_std_price = price_unit
                            elif float_is_zero(product_tot_qty_available +
                                               qty_done,
                                               precision_rounding=rounding):
                                new_std_price = price_unit
                            else:
                                new_std_price = (
                                    (new_std_price * product_tot_qty_available)
                                    + (price_unit * qty_done)) / (
                                        product_tot_qty_available + qty_done)
                            loc.get((ml.location_dest_id.id, ml.product_id.id,
                                     ml.lot_id.id, ml.package_id.id)).update(
                                         {'cost': new_std_price})
                            # Save price_standar
                            self.env['product.price.history.card'].create({
                                'datetime':
                                ml.set_date or ml.date,
                                'cost':
                                loc.get((ml.location_dest_id.id,
                                         ml.product_id.id, ml.lot_id.id,
                                         ml.package_id.id)).get('cost'),
                                'location_id':
                                ml.location_dest_id.id,
                                'product_id':
                                ml.product_id.id,
                                'lot_id':
                                ml.lot_id.id,
                                'package_id':
                                ml.package_id.id
                            })

                    loc.get((ml.location_dest_id.id, ml.product_id.id,
                             ml.lot_id.id, ml.package_id.id)).update({
                                 'product_tot_qty_available':
                                 loc.get(
                                     (ml.location_dest_id.id, ml.product_id.id,
                                      ml.lot_id.id, ml.package_id.id
                                      )).get('product_tot_qty_available') +
                                 qty_done
                             })
                    loc.get((ml.location_id.id, ml.product_id.id, ml.lot_id.id,
                             ml.package_id.id)).update({
                                 'product_tot_qty_available':
                                 loc.get((ml.location_id.id, ml.product_id.id,
                                          ml.lot_id.id, ml.package_id.id
                                          )).get('product_tot_qty_available') -
                                 qty_done
                             })

                    # Add last date move line
                    loc.update(
                        {'move_line_last_datetime': ml.set_date or ml.date})
                    if_move_line = True

                # Save last standard_price
                if sorted_move_lines:
                    for key in loc.keys():
                        if key != "move_line_last_datetime":
                            if loc.get(key).get('usage') == 'internal':
                                quant = self.env['stock.quant'].search(
                                    [('company_id', '=',
                                      self.env.user.company_id.id),
                                     ('location_id', '=', key[0]),
                                     ('product_id', '=',
                                      loc.get(key).get('product_id')),
                                     ('lot_id', '=',
                                      loc.get(key).get('lot_id')),
                                     ('package_id', '=',
                                      loc.get(key).get('package_id'))],
                                    limit=1)
                                new_std_price = loc.get(key).get('cost')
                                quant.with_context(not_create=True).write(
                                    {'standard_price': new_std_price})
                                quant.with_context(
                                    update_card=True).show_stock_card()
            if if_move_line:
                # update dict loc
                self.last_location_dict = loc

        else:
            loc = False
            for data in ml_data:
                move_lines = self.env['stock.move.line'].search(
                    data['__domain'] + [('state', '=', 'done')],
                    order='date asc')
                sorted_move_lines = sorted(move_lines,
                                           key=lambda a: a.set_date or a.date)
                loc = {(sml.location_dest_id.id, sml.product_id.id,
                        sml.lot_id.id, sml.package_id.id): {
                            'product_tot_qty_available': 0.0,
                            'cost': 0.0,
                            'product_id': sml.product_id.id,
                            'usage': sml.location_dest_id.usage,
                            'lot_id': sml.lot_id.id,
                            'package_id': sml.package_id.id
                        }
                       for sml in sorted_move_lines}

                loc.update({(sml.location_id.id, sml.product_id.id,
                             sml.lot_id.id, sml.package_id.id): {
                                 'product_tot_qty_available': 0.0,
                                 'cost': 0.0,
                                 'product_id': sml.product_id.id,
                                 'usage': sml.location_id.usage,
                                 'lot_id': sml.lot_id.id,
                                 'package_id': sml.package_id.id
                             }
                            for sml in sorted_move_lines})

                for index, ml in enumerate(sorted_move_lines):
                    qty_done = ml.qty_done
                    qty_done = ml.qty_done
                    if tax_included == 'True':
                        price_unit = abs(ml.move_id._get_price_unit_bo())
                    else:
                        price_unit = abs(ml.move_id.price_unit)
                    rounding = ml.product_id.uom_id.rounding
                    if not index:
                        loc.get(
                            (ml.location_dest_id.id, ml.product_id.id,
                             ml.lot_id.id,
                             ml.package_id.id)).update({'cost': price_unit})
                        price_used = ml.product_id.get_history_price(
                            self.env.user.company_id.id,
                            date=ml.set_date or ml.date,
                        )
                        # Save price_standar
                        self.env['product.price.history.card'].create({
                            'datetime':
                            ml.set_date or ml.date,
                            'cost':
                            loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')
                            or price_used,
                            'location_id':
                            ml.location_dest_id.id,
                            'product_id':
                            ml.product_id.id,
                            'lot_id':
                            ml.lot_id.id,
                            'package_id':
                            ml.package_id.id
                        })
                    else:
                        if ml.location_id.usage not in [
                                'internal', 'inventory'
                        ] and ml.location_dest_id.usage == 'internal':
                            product_tot_qty_available = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id
                                 )).get('product_tot_qty_available')
                            new_std_price = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')
                            if float_is_zero(product_tot_qty_available,
                                             precision_rounding=rounding):
                                new_std_price = price_unit
                            elif float_is_zero(product_tot_qty_available +
                                               qty_done,
                                               precision_rounding=rounding):
                                new_std_price = price_unit
                            else:
                                new_std_price = (
                                    (new_std_price * product_tot_qty_available)
                                    + (price_unit * qty_done)) / (
                                        product_tot_qty_available + qty_done)
                            loc.get((ml.location_dest_id.id, ml.product_id.id,
                                     ml.lot_id.id, ml.package_id.id)).update(
                                         {'cost': new_std_price})
                            # Save price_standar
                            self.env['product.price.history.card'].create({
                                'datetime':
                                ml.set_date or ml.date,
                                'cost':
                                loc.get((ml.location_dest_id.id,
                                         ml.product_id.id, ml.lot_id.id,
                                         ml.package_id.id)).get('cost'),
                                'location_id':
                                ml.location_dest_id.id,
                                'product_id':
                                ml.product_id.id,
                                'lot_id':
                                ml.lot_id.id,
                                'package_id':
                                ml.package_id.id
                            })

                        if ml.location_id.usage == 'internal' and ml.location_dest_id.usage == 'internal':
                            product_tot_qty_available = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id
                                 )).get('product_tot_qty_available')
                            price_unit = loc.get(
                                (ml.location_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')

                            new_std_price = loc.get(
                                (ml.location_dest_id.id, ml.product_id.id,
                                 ml.lot_id.id, ml.package_id.id)).get('cost')
                            if float_is_zero(product_tot_qty_available,
                                             precision_rounding=rounding):
                                new_std_price = price_unit
                            elif float_is_zero(product_tot_qty_available +
                                               qty_done,
                                               precision_rounding=rounding):
                                new_std_price = price_unit
                            else:
                                new_std_price = (
                                    (new_std_price * product_tot_qty_available)
                                    + (price_unit * qty_done)) / (
                                        product_tot_qty_available + qty_done)
                            loc.get((ml.location_dest_id.id, ml.product_id.id,
                                     ml.lot_id.id, ml.package_id.id)).update(
                                         {'cost': new_std_price})
                            # Save price_standar
                            self.env['product.price.history.card'].create({
                                'datetime':
                                ml.set_date or ml.date,
                                'cost':
                                loc.get((ml.location_dest_id.id,
                                         ml.product_id.id, ml.lot_id.id,
                                         ml.package_id.id)).get('cost'),
                                'location_id':
                                ml.location_dest_id.id,
                                'product_id':
                                ml.product_id.id,
                                'lot_id':
                                ml.lot_id.id,
                                'package_id':
                                ml.package_id.id
                            })

                    loc.get((ml.location_dest_id.id, ml.product_id.id,
                             ml.lot_id.id, ml.package_id.id)).update({
                                 'product_tot_qty_available':
                                 loc.get(
                                     (ml.location_dest_id.id, ml.product_id.id,
                                      ml.lot_id.id, ml.package_id.id
                                      )).get('product_tot_qty_available') +
                                 qty_done
                             })
                    loc.get((ml.location_id.id, ml.product_id.id, ml.lot_id.id,
                             ml.package_id.id)).update({
                                 'product_tot_qty_available':
                                 loc.get((ml.location_id.id, ml.product_id.id,
                                          ml.lot_id.id, ml.package_id.id
                                          )).get('product_tot_qty_available') -
                                 qty_done
                             })
                    # Add last date move line
                    loc.update(
                        {'move_line_last_datetime': ml.set_date or ml.date})

                # Save last standard_price
                if sorted_move_lines:
                    for key in loc.keys():
                        if key != "move_line_last_datetime":
                            if loc.get(key).get('usage') == 'internal':
                                quant = self.env['stock.quant'].search(
                                    [('company_id', '=',
                                      self.env.user.company_id.id),
                                     ('location_id', '=', key[0]),
                                     ('product_id', '=',
                                      loc.get(key).get('product_id')),
                                     ('lot_id', '=',
                                      loc.get(key).get('lot_id')),
                                     ('package_id', '=',
                                      loc.get(key).get('package_id'))],
                                    limit=1)
                                new_std_price = loc.get(key).get('cost')
                                quant.with_context(not_create=True).write(
                                    {'standard_price': new_std_price})
                                quant.with_context(
                                    update_card=True).show_stock_card()
            # update dict loc
            self.last_location_dict = loc
Exemplo n.º 53
0
    def test_payslip_accounting(self, hr_payslip_run_name=None):
        _logger.debug('hr_payslip_run_name = ' + str(hr_payslip_run_name))
        '''
        Delete unposted moves related to payslips in the batch
        Skip payslips with posted move
        Create one move (per journal/month) with totals for the batch/journal/month
            account_move.ref = _('Payroll')
            account_move_line.name = hr_payslip_run_name or "/"
            account_move_line.narration = ""
        Set move_id & date on the payslips
        '''
        # check
        for slip in self:
            if not slip.journal_id:
                raise UserError('Slip %s should have a journal!' % (slip.ref))
            if not slip.date and not slip.date_to:
                raise UserError('Slip %s needs "date" or "date_to"!' % (slip.ref))

        # delete account.move (if not posted)
        # group payslips per journal/month (skip if posted)
        grouped_payslips = []
        for slip in self:
            if slip.move_id:
                if slip.move_id.state == 'posted':
                    continue
                else:
                    self.env['account.move'].browse(slip.move_id.id).unlink()
            date = slip.date or slip.date_to
            year_month = date.strftime('%Y-%m')
            appended = False
            for group in grouped_payslips:
                if group['journal_id'] == slip.journal_id.id and group['month'] == year_month:
                    group['payslips'].append(slip.id)
                    group['date_to'] = max(group['date_to'], slip.date or slip.date_to)
                    appended = True
            if not appended:
                grouped_payslips.append({
                    'journal_id': slip.journal_id.id,
                    'month': year_month,
                    'payslips': [slip.id],
                    'date_to': date,
                })

        # TODO: Move to l10n_no_payroll
        loennsart_fp_i_aar = self.env['res.field.value'].search(
            [('model','=','res.company'),('field_id','=',self.env.ref('l10n_no_payroll.res_field_l10n_no_loennsart_fp_i_aar').id)]
        ).ensure_one().reference_value
        loennsart_fp_i_fjor = self.env['res.field.value'].search(
            [('model','=','res.company'),('field_id','=',self.env.ref('l10n_no_payroll.res_field_l10n_no_loennsart_fp_i_fjor').id)]
        ).ensure_one().reference_value

        # create one account.move per journal/month
        # update payslip.move_id & payslip.date
        precision = self.env['decimal.precision'].precision_get('Payroll')
        account_moves = []
        for group in grouped_payslips:
            account_move_lines = []
            total = 0.0
            name = hr_payslip_run_name or "/"
            move_dict = {
                'ref': _('Payroll'),
                'journal_id': group['journal_id'],
                'date': group['date_to'],
            }

            for slip in self.browse(group['payslips']):
                if not hr_payslip_run_name:
                    move_dict = {
                        'ref': slip.number,
                        'journal_id': slip.journal_id.id,
                        'date': group['date_to'],
                    }
                contract_analytic_account_id = None
                if slip.contract_id and slip.contract_id.analytic_account_id:
                    contract_analytic_account_id = slip.contract_id.analytic_account_id.id

                line_ids = slip.line_ids
                for line in line_ids:
                    amount = slip.credit_note and -line.total or line.total
                    if float_is_zero(amount, precision_digits=precision):
                        continue

                    def _add(new_amount, new_account, new_partner_id=False):
                        # Also using these variables:
                        # precision
                        # line
                        # contract_analytic_account_id
                        # name
                        # slip
                        # group
                        # account_move_lines
                        new_amount = float_round(new_amount, precision_digits=precision)
                        if new_account.user_type_id.include_initial_balance:
                            new_analytic_account_id = None
                        else:
                            new_analytic_account_id = line.analytic_account_id.id or line.salary_rule_id.analytic_account_id.id or contract_analytic_account_id
                        new = {
                            'name': name,
                            'partner_id': new_partner_id,
                            'account_id': new_account.id,
                            'journal_id': slip.journal_id.id,
                            'date': group['date_to'],
                            'amount': new_amount,
                            'analytic_account_id': new_analytic_account_id,
                            'tax_line_id': line.salary_rule_id.account_tax_id.id,
                        }
                        compare = "start"
                        for aml in account_move_lines:
                            compare = "same"
                            for key in ['name', 'partner_id', 'account_id', 'analytic_account_id', 'tax_line_id']:
                                if aml[key] != new[key]:
                                    compare = "different"
                                    break
                            if compare == "same":
                                aml['amount'] += new_amount
                                break
                        if compare != "same":
                            account_move_lines.append(new)

                    debit_account = line.salary_rule_id.account_debit
                    if debit_account:
                        partner_id = line._get_partner_id(credit_account=False)
                        _add(amount, debit_account, partner_id)
                        total += amount

                    credit_account = line.salary_rule_id.account_credit
                    if credit_account:
                        partner_id = line._get_partner_id(credit_account=True)
                        _add(-amount, credit_account, partner_id)
                        total -= amount

                    if self.env.company.partner_id.country_id.code == 'NO':
                        # TODO: move to a method in l10n_no_payroll; call the method from here (depends on def _add)
                        c_field = {}
                        c_records = self.env.company.field_value_hr_ids
                        for c_record in c_records:
                            if c_record.selection_value_id:
                                c_field[c_record.field_code] = c_record.selection_value_id.code
                            elif c_record.reference_value:
                                c_field[c_record.field_code] = c_record.reference_value
                            elif c_record.value:
                                c_field[c_record.field_code] = c_record.value

                        aga = float(c_field['l10n_no_Grunnlagsprosent']) / 100.0
                        fp = float(c_field['l10n_no_fp_prosent']) / 100.0
                        fp_senior = float(c_field['l10n_no_fp_prosent_senior']) / 100.0

                        beregn_aga = line.salary_rule_id.field_value_ids.filtered(lambda x: x.field_code == 'l10n_no_BeregnAga')
                        if beregn_aga:
                            beregn_aga = beregn_aga.selection_value_id

                        beregn_fp = line.salary_rule_id.field_value_ids.filtered(lambda x: x.field_code == 'l10n_no_BeregnFP')
                        if beregn_fp:
                            beregn_fp = bool(beregn_fp.value) and beregn_fp.value not in ('False', 'false', 'FALSE', '0')

                        if beregn_aga:
                            _add(amount * aga, c_field['l10n_no_aga_konto'])
                            _add(-amount * aga, c_field['l10n_no_aga_motkonto'])
                        if beregn_fp:
                            year = line.slip_id.date_to.year
                            birthyear = line.employee_id.birthday.year
                            if year - birthyear >= 59:
                                fp = fp_senior
                            _add(amount * fp, c_field['l10n_no_fp_konto'])
                            _add(-amount * fp, c_field['l10n_no_fp_motkonto'])
                            _add(amount * aga * fp, c_field['l10n_no_aga_fp_konto'])
                            _add(-amount * aga * fp, c_field['l10n_no_aga_fp_motkonto'])

                        if line.salary_rule_id in (loennsart_fp_i_aar, loennsart_fp_i_fjor):
                            _add(-amount * aga, c_field['l10n_no_aga_fp_konto'])
                            _add(amount * aga, c_field['l10n_no_aga_fp_motkonto'])

            if not float_is_zero(total, precision_digits=precision):
                acc_journal = self.env['account.journal'].browse(group['journal_id'])
                acc_id = None
                if total > 0:
                    acc_id = acc_journal.default_account_id.id
                    if not acc_id:
                        raise UserError(
                            _('The Expense Journal "%s" has not properly configured the Default Account!') % (
                                acc_journal.name))
                elif total < 0:
                    acc_id = acc_journal.default_account_id.id
                    if not acc_id:
                        raise UserError(
                            _('The Expense Journal "%s" has not properly configured the Default Account!') % (
                                acc_journal.name))
                account_move_lines.append({
                    'name': name,
                    'account_id': acc_id,
                    'journal_id': acc_journal.id,
                    'date': group['date_to'],
                    'amount': -total,
                })

            for aml in account_move_lines:
                if aml['amount'] > 0:
                    aml['debit'] = aml['amount']
                elif aml['amount'] < 0:
                    aml['credit'] = -aml['amount']
                del aml['amount']
            account_move_lines = [(0, 0, aml) for aml in account_move_lines]
            move_dict['line_ids'] = account_move_lines
            move = self.env['account.move'].create(move_dict)
            account_moves.append(move)
            for slip in self.browse(group['payslips']):
                slip.write({'move_id': move.id, 'date': group['date_to']})

        return account_moves
Exemplo n.º 54
0
    def action_invoice_create(self, grouped=False, final=False):
        """
        Create the invoice associated to the SO.
        :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by
                        (partner_invoice_id, currency)
        :param final: if True, refunds will be generated if necessary
        :returns: list of created invoices
        """
        inv_obj = self.env['account.invoice']
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        invoices = {}
        references = {}
        for order in self:
            group_key = order.id if grouped else (order.partner_invoice_id.id,
                                                  order.currency_id.id)
            for line in order.order_line.sorted(
                    key=lambda l: l.qty_to_invoice < 0):
                if float_is_zero(line.qty_to_invoice,
                                 precision_digits=precision):
                    continue
                if group_key not in invoices:
                    inv_data = order._prepare_invoice()
                    invoice = inv_obj.create(inv_data)
                    invoice.sudo().write(
                        {'user_id': self.user_id and self.user_id.id})
                    references[invoice] = order
                    invoices[group_key] = invoice
                elif group_key in invoices:
                    vals = {}
                    if order.name not in invoices[group_key].origin.split(
                            ', '):
                        vals['origin'] = invoices[
                            group_key].origin + ', ' + order.name
                    if order.client_order_ref and order.client_order_ref not in invoices[
                            group_key].name.split(', '):
                        vals['name'] = invoices[
                            group_key].name + ', ' + order.client_order_ref
                    invoices[group_key].write(vals)
                if line.qty_to_invoice > 0:
                    line.invoice_line_create(invoices[group_key].id,
                                             line.qty_to_invoice)
                elif line.qty_to_invoice < 0 and final:
                    line.invoice_line_create(invoices[group_key].id,
                                             line.qty_to_invoice)

            if references.get(invoices.get(group_key)):
                if order not in references[invoices[group_key]]:
                    references[invoice] = references[invoice] | order

        if not invoices:
            raise UserError(_('There is no invoicable line.'))

        for invoice in invoices.values():
            if not invoice.invoice_line_ids:
                raise UserError(_('There is no invoicable line.'))
            # If invoice is negative, do a refund invoice instead
            if invoice.amount_untaxed < 0:
                invoice.type = 'out_refund'
                for line in invoice.invoice_line_ids:
                    line.quantity = -line.quantity
            # Use additional field helper function (for account extensions)
            for line in invoice.invoice_line_ids:
                line._set_additional_fields(invoice)
            # Necessary to force computation of taxes. In account_invoice, they are triggered
            # by onchanges, which are not triggered when doing a create.
            invoice.compute_taxes()
            invoice.message_post_with_view(
                'mail.message_origin_link',
                values={
                    'self': invoice,
                    'origin': references[invoice]
                },
                subtype_id=self.env.ref('mail.mt_note').id)
        return [inv.id for inv in invoices.values()]
Exemplo n.º 55
0
    def do_merge(self,
                 keep_references=True,
                 date_invoice=False,
                 remove_empty_invoice_lines=True):
        """
        To merge similar type of account invoices.
        Invoices will only be merged if:
        * Account invoices are in draft
        * Account invoices belong to the same partner
        * Account invoices are have same company, partner, address, currency,
          journal, currency, salesman, account, type
        Lines will only be merged if:
        * Invoice lines are exactly the same except for the quantity and unit

         @param self: The object pointer.
         @param keep_references: If True, keep reference of original invoices

         @return: new account invoice id

        """
        def make_key(br, fields):
            list_key = []
            for field in fields:
                field_val = getattr(br, field)
                if field in ('product_id', 'account_id'):
                    if not field_val:
                        field_val = False
                if (isinstance(field_val, browse_record)
                        and field != 'invoice_line_tax_ids'
                        and field != 'agents' and field != 'sale_line_ids'):
                    field_val = field_val.id
                elif isinstance(field_val, browse_null):
                    field_val = False
                elif (isinstance(field_val, list)
                      or field == 'invoice_line_tax_ids' or field == 'agents'
                      or  # CMNT añadido
                      field == 'sale_line_ids'):
                    field_val = ((6, 0, tuple([v.id for v in field_val])), )
                list_key.append((field, field_val))
            list_key.sort()
            return tuple(list_key)

        # compute what the new invoices should contain
        new_invoices = {}
        seen_origins = {}
        seen_client_refs = {}

        for account_invoice in self._get_draft_invoices():
            invoice_key = make_key(account_invoice,
                                   self._get_invoice_key_cols())
            new_invoice = new_invoices.setdefault(invoice_key, ({}, []))
            origins = seen_origins.setdefault(invoice_key, set())
            client_refs = seen_client_refs.setdefault(invoice_key, set())
            new_invoice[1].append(account_invoice.id)
            invoice_infos = new_invoice[0]
            if not invoice_infos:
                invoice_infos.update(
                    self._get_first_invoice_fields(account_invoice))
                origins.add(account_invoice.origin)
                client_refs.add(account_invoice.reference)
                if not keep_references:
                    invoice_infos.pop('name')
            else:
                if account_invoice.name and keep_references:
                    invoice_infos['name'] = \
                        (invoice_infos['name'] or '') + ' ' + \
                        account_invoice.name
                if account_invoice.origin and \
                        account_invoice.origin not in origins:
                    invoice_infos['origin'] = \
                        (invoice_infos['origin'] or '') + ' ' + \
                        account_invoice.origin
                    origins.add(account_invoice.origin)
                if account_invoice.reference \
                        and account_invoice.reference not in client_refs:
                    invoice_infos['reference'] = \
                        (invoice_infos['reference'] or '') + ' ' + \
                        account_invoice.reference
                    client_refs.add(account_invoice.reference)

            for invoice_line in account_invoice.invoice_line_ids:
                line_key = make_key(invoice_line,
                                    self._get_invoice_line_key_cols())

                o_line = invoice_infos['invoice_line_ids'].\
                    setdefault(line_key, {})

                if o_line:
                    # merge the line with an existing line
                    o_line['quantity'] += invoice_line.quantity
                else:
                    # append a new "standalone" line
                    o_line['quantity'] = invoice_line.quantity

        allinvoices = []
        allnewinvoices = []
        invoices_info = {}
        old_invoices = self.env['account.invoice']
        qty_prec = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        for invoice_key, (invoice_data, old_ids) in new_invoices.items():
            # skip merges with only one invoice
            if len(old_ids) < 2:
                allinvoices += (old_ids or [])
                continue
            # cleanup invoice line data
            for key, value in invoice_data['invoice_line_ids'].items():
                value.update(dict(key))

            if remove_empty_invoice_lines:
                invoice_data['invoice_line_ids'] = [
                    (0, 0, value)
                    for value in invoice_data['invoice_line_ids'].values()
                    if not float_is_zero(value['quantity'],
                                         precision_digits=qty_prec)
                ]
            else:
                invoice_data['invoice_line_ids'] = [
                    (0, 0, value)
                    for value in invoice_data['invoice_line_ids'].values()
                ]

            if date_invoice:
                invoice_data['date_invoice'] = date_invoice

            # create the new invoice
            newinvoice = self.with_context(is_merge=True).create(invoice_data)
            invoices_info.update({newinvoice.id: old_ids})
            allinvoices.append(newinvoice.id)
            allnewinvoices.append(newinvoice)
            # cancel old invoices
            old_invoices = self.env['account.invoice'].browse(old_ids)
            old_invoices.with_context(is_merge=True).action_invoice_cancel()

        # Make link between original sale order
        # None if sale is not installed
        invoice_line_obj = self.env['account.invoice.line']
        for new_invoice_id in invoices_info:
            if 'sale.order' in self.env.registry:
                sale_todos = old_invoices.mapped(
                    'invoice_line_ids.sale_line_ids.order_id')
                for org_so in sale_todos:
                    for so_line in org_so.order_line:
                        invoice_line = invoice_line_obj.search([
                            ('id', 'in', so_line.invoice_lines.ids),
                            ('invoice_id', '=', new_invoice_id)
                        ])
                        if invoice_line:
                            so_line.write(
                                {'invoice_lines': [(6, 0, invoice_line.ids)]})

        # recreate link (if any) between original analytic account line
        # (invoice time sheet for example) and this new invoice
        anal_line_obj = self.env['account.analytic.line']
        if 'invoice_id' in anal_line_obj._fields:
            for new_invoice_id in invoices_info:
                anal_todos = anal_line_obj.search([
                    ('invoice_id', 'in', invoices_info[new_invoice_id])
                ])
                anal_todos.write({'invoice_id': new_invoice_id})

        for new_invoice in allnewinvoices:
            new_invoice.compute_taxes()

        return invoices_info
Exemplo n.º 56
0
 def _need_quantity_propagation(self, move, qty):
     return move.move_dest_ids and not float_is_zero(qty, precision_rounding=move.product_uom.rounding)
Exemplo n.º 57
0
    def _get_partner_move_lines(self, account_type, date_from, target_move,
                                period_length):
        # This method can receive the context key 'include_nullified_amount' {Boolean}
        # Do an invoice and a payment and unreconcile. The amount will be nullified
        # By default, the partner wouldn't appear in this report.
        # The context key allow it to appear
        # In case of a period_length of 30 days as of 2019-02-08, we want the following periods:
        # Name       Stop         Start
        # 1 - 30   : 2019-02-07 - 2019-01-09
        # 31 - 60  : 2019-01-08 - 2018-12-10
        # 61 - 90  : 2018-12-09 - 2018-11-10
        # 91 - 120 : 2018-11-09 - 2018-10-11
        # +120     : 2018-10-10
        ctx = self._context
        periods = {}
        date_from = fields.Date.from_string(date_from)
        start = date_from
        for i in range(5)[::-1]:
            stop = start - relativedelta(days=period_length)
            period_name = str((5 - (i + 1)) * period_length + 1) + '-' + str(
                (5 - i) * period_length)
            period_stop = (start - relativedelta(days=1)).strftime('%Y-%m-%d')
            if i == 0:
                period_name = '+' + str(4 * period_length)
            periods[str(i)] = {
                'name': period_name,
                'stop': period_stop,
                'start': (i != 0 and stop.strftime('%Y-%m-%d') or False),
            }
            start = stop

        res = []
        total = []
        partner_clause = ''
        cr = self.env.cr
        user_company = self.env.user.company_id
        user_currency = user_company.currency_id
        company_ids = self._context.get('company_ids') or [user_company.id]
        move_state = ['draft', 'posted']
        if target_move == 'posted':
            move_state = ['posted']
        arg_list = (tuple(move_state), tuple(account_type))
        #build the reconciliation clause to see what partner needs to be printed
        reconciliation_clause = '(l.reconciled IS FALSE)'
        cr.execute(
            'SELECT debit_move_id, credit_move_id FROM account_partial_reconcile where max_date > %s',
            (date_from, ))
        reconciled_after_date = []
        for row in cr.fetchall():
            reconciled_after_date += [row[0], row[1]]
        if reconciled_after_date:
            reconciliation_clause = '(l.reconciled IS FALSE OR l.id IN %s)'
            arg_list += (tuple(reconciled_after_date), )
        if ctx.get('partner_ids'):
            partner_clause = 'AND (l.partner_id IN %s)'
            arg_list += (tuple(ctx['partner_ids'].ids), )
        if ctx.get('partner_categories'):
            partner_clause += 'AND (l.partner_id IN %s)'
            partner_ids = self.env['res.partner'].search([
                ('category_id', 'in', ctx['partner_categories'].ids)
            ]).ids
            arg_list += (tuple(partner_ids or [0]), )
        arg_list += (date_from, tuple(company_ids))
        query = '''
            SELECT DISTINCT l.partner_id, UPPER(res_partner.name)
            FROM account_move_line AS l left join res_partner on l.partner_id = res_partner.id, account_account, account_move am
            WHERE (l.account_id = account_account.id)
                AND (l.move_id = am.id)
                AND (am.state IN %s)
                AND (account_account.internal_type IN %s)
                AND ''' + reconciliation_clause + partner_clause + '''
                AND (l.date <= %s)
                AND l.company_id IN %s
            ORDER BY UPPER(res_partner.name)'''
        cr.execute(query, arg_list)

        partners = cr.dictfetchall()
        # put a total of 0
        for i in range(7):
            total.append(0)

        # Build a string like (1,2,3) for easy use in SQL query
        partner_ids = [
            partner['partner_id'] for partner in partners
            if partner['partner_id']
        ]
        lines = dict(
            (partner['partner_id'] or False, []) for partner in partners)
        if not partner_ids:
            return [], [], {}

        # Use one query per period and store results in history (a list variable)
        # Each history will contain: history[1] = {'<partner_id>': <partner_debit-credit>}
        history = []
        for i in range(5):
            args_list = (
                tuple(move_state),
                tuple(account_type),
                tuple(partner_ids),
            )
            dates_query = '(COALESCE(l.date_maturity,l.date)'

            if periods[str(i)]['start'] and periods[str(i)]['stop']:
                dates_query += ' BETWEEN %s AND %s)'
                args_list += (periods[str(i)]['start'],
                              periods[str(i)]['stop'])
            elif periods[str(i)]['start']:
                dates_query += ' >= %s)'
                args_list += (periods[str(i)]['start'], )
            else:
                dates_query += ' <= %s)'
                args_list += (periods[str(i)]['stop'], )
            args_list += (date_from, tuple(company_ids))

            query = '''SELECT l.id
                    FROM account_move_line AS l, account_account, account_move am
                    WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
                        AND (am.state IN %s)
                        AND (account_account.internal_type IN %s)
                        AND ((l.partner_id IN %s) OR (l.partner_id IS NULL))
                        AND ''' + dates_query + '''
                    AND (l.date <= %s)
                    AND l.company_id IN %s
                    ORDER BY COALESCE(l.date_maturity, l.date)'''
            cr.execute(query, args_list)
            partners_amount = {}
            aml_ids = cr.fetchall()
            aml_ids = aml_ids and [x[0] for x in aml_ids] or []
            for line in self.env['account.move.line'].browse(
                    aml_ids).with_context(prefetch_fields=False):
                partner_id = line.partner_id.id or False
                if partner_id not in partners_amount:
                    partners_amount[partner_id] = 0.0
                line_amount = line.company_id.currency_id._convert(
                    line.balance, user_currency, user_company, date_from)
                if user_currency.is_zero(line_amount):
                    continue
                for partial_line in line.matched_debit_ids:
                    if partial_line.max_date <= date_from:
                        line_amount += partial_line.company_id.currency_id._convert(
                            partial_line.amount, user_currency, user_company,
                            date_from)
                for partial_line in line.matched_credit_ids:
                    if partial_line.max_date <= date_from:
                        line_amount -= partial_line.company_id.currency_id._convert(
                            partial_line.amount, user_currency, user_company,
                            date_from)

                if not self.env.user.company_id.currency_id.is_zero(
                        line_amount):
                    partners_amount[partner_id] += line_amount
                    lines.setdefault(partner_id, [])
                    lines[partner_id].append({
                        'line': line,
                        'amount': line_amount,
                        'period': i + 1,
                    })
            history.append(partners_amount)

        # This dictionary will store the not due amount of all partners
        undue_amounts = {}
        query = '''SELECT l.id
                FROM account_move_line AS l, account_account, account_move am
                WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
                    AND (am.state IN %s)
                    AND (account_account.internal_type IN %s)
                    AND (COALESCE(l.date_maturity,l.date) >= %s)\
                    AND ((l.partner_id IN %s) OR (l.partner_id IS NULL))
                AND (l.date <= %s)
                AND l.company_id IN %s
                ORDER BY COALESCE(l.date_maturity, l.date)'''
        cr.execute(query, (tuple(move_state), tuple(account_type), date_from,
                           tuple(partner_ids), date_from, tuple(company_ids)))
        aml_ids = cr.fetchall()
        aml_ids = aml_ids and [x[0] for x in aml_ids] or []
        for line in self.env['account.move.line'].browse(aml_ids):
            partner_id = line.partner_id.id or False
            if partner_id not in undue_amounts:
                undue_amounts[partner_id] = 0.0
            line_amount = line.company_id.currency_id._convert(
                line.balance, user_currency, user_company, date_from)
            if user_currency.is_zero(line_amount):
                continue
            for partial_line in line.matched_debit_ids:
                if partial_line.max_date <= date_from:
                    line_amount += partial_line.company_id.currency_id._convert(
                        partial_line.amount, user_currency, user_company,
                        date_from)
            for partial_line in line.matched_credit_ids:
                if partial_line.max_date <= date_from:
                    line_amount -= partial_line.company_id.currency_id._convert(
                        partial_line.amount, user_currency, user_company,
                        date_from)
            if not self.env.user.company_id.currency_id.is_zero(line_amount):
                undue_amounts[partner_id] += line_amount
                lines.setdefault(partner_id, [])
                lines[partner_id].append({
                    'line': line,
                    'amount': line_amount,
                    'period': 6,
                })

        for partner in partners:
            if partner['partner_id'] is None:
                partner['partner_id'] = False
            at_least_one_amount = False
            values = {}
            undue_amt = 0.0
            if partner[
                    'partner_id'] in undue_amounts:  # Making sure this partner actually was found by the query
                undue_amt = undue_amounts[partner['partner_id']]

            total[6] = total[6] + undue_amt
            values['direction'] = undue_amt
            if not float_is_zero(values['direction'],
                                 precision_rounding=self.env.user.company_id.
                                 currency_id.rounding):
                at_least_one_amount = True

            for i in range(5):
                during = False
                if partner['partner_id'] in history[i]:
                    during = [history[i][partner['partner_id']]]
                # Adding counter
                total[(i)] = total[(i)] + (during and during[0] or 0)
                values[str(i)] = during and during[0] or 0.0
                if not float_is_zero(values[str(i)],
                                     precision_rounding=self.env.user.
                                     company_id.currency_id.rounding):
                    at_least_one_amount = True
            values['total'] = sum([values['direction']] +
                                  [values[str(i)] for i in range(5)])
            ## Add for total
            total[(i + 1)] += values['total']
            values['partner_id'] = partner['partner_id']
            if partner['partner_id']:
                browsed_partner = self.env['res.partner'].browse(
                    partner['partner_id'])
                values['name'] = browsed_partner.name and len(
                    browsed_partner.name) >= 45 and browsed_partner.name[
                        0:40] + '...' or browsed_partner.name
                values['trust'] = browsed_partner.trust
            else:
                values['name'] = _('Unknown Partner')
                values['trust'] = False

            if at_least_one_amount or (
                    self._context.get('include_nullified_amount')
                    and lines[partner['partner_id']]):
                res.append(values)

        return res, total, lines
Exemplo n.º 58
0
    def _get_partner_move_lines_custom(self, account_type, date_from,
                                       target_move, period_length):
        periods = {}
        start = datetime.strptime(date_from, "%Y-%m-%d")
        for i in range(5)[::-1]:
            stop = start - relativedelta(days=period_length)
            period_name = str((5 - (i + 1)) * period_length + 1) + '-' + str(
                (5 - i) * period_length)
            period_stop = (start - relativedelta(days=1)).strftime('%Y-%m-%d')
            if i == 0:
                period_name = '+' + str(4 * period_length)
            periods[str(i)] = {
                'name': period_name,
                'stop': period_stop,
                'start': (i != 0 and stop.strftime('%Y-%m-%d') or False),
            }
            start = stop
        res = []
        total = []
        cr = self.env.cr
        user_company = self.env.user.company_id
        user_currency = user_company.currency_id
        ResCurrency = self.env['res.currency'].with_context(date=date_from)
        company_ids = self._context.get('company_ids') or [user_company.id]
        move_state = ['draft', 'posted']
        if target_move == 'posted':
            move_state = ['posted']
        arg_list = (tuple(move_state), tuple(account_type))

        reconciliation_clause = '(l.reconciled IS FALSE)'

        cr.execute(
            'SELECT debit_move_id, credit_move_id FROM account_partial_reconcile where max_date > %s',
            (date_from, ))
        reconciled_after_date = []
        for row in cr.fetchall():
            reconciled_after_date += [row[0], row[1]]
        if reconciled_after_date:
            reconciliation_clause = '(l.reconciled IS FALSE OR l.id IN %s)'
            arg_list += (tuple(reconciled_after_date), )
        arg_list += (date_from, tuple(company_ids))

        query = '''
            SELECT DISTINCT l.partner_id, UPPER(res_partner.name)
            FROM account_move_line AS l left join res_partner on l.partner_id = res_partner.id, account_account, account_move am
            WHERE (l.account_id = account_account.id)
                AND (l.move_id = am.id)
                AND (am.state IN %s)
                AND (account_account.internal_type IN %s)
                AND ''' + reconciliation_clause + '''
                AND (l.date <= %s)
                AND l.company_id IN %s
            ORDER BY UPPER(res_partner.name)'''
        cr.execute(query, arg_list)

        partners = cr.dictfetchall()

        # put a total of 0
        for i in range(7):
            total.append(0)

        # Build a string like (1,2,3) for easy use in SQL query
        partner_ids = [
            partner['partner_id'] for partner in partners
            if partner['partner_id']
        ]
        lines = dict(
            (partner['partner_id'] or False, []) for partner in partners)
        if not partner_ids:
            return [], [], {}

        # This dictionary will store the not due amount of all partners
        undue_amounts = {}
        query = '''SELECT l.id
                FROM account_move_line AS l, account_account, account_move am
                WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
                    AND (am.state IN %s)
                    AND (account_account.internal_type IN %s)
                    AND (COALESCE(l.date_maturity,l.date) >= %s)\
                    AND ((l.partner_id IN %s) OR (l.partner_id IS NULL))
                AND (l.date <= %s)
                AND l.company_id IN %s'''
        cr.execute(query, (tuple(move_state), tuple(account_type), date_from,
                           tuple(partner_ids), date_from, tuple(company_ids)))
        aml_ids = cr.fetchall()
        aml_ids = aml_ids and [x[0] for x in aml_ids] or []
        for line in self.env['account.move.line'].browse(aml_ids):
            partner_id = line.partner_id.id or False
            if partner_id not in undue_amounts:
                undue_amounts[partner_id] = 0.0
            line_amount = ResCurrency._compute(line.company_id.currency_id,
                                               user_currency, line.balance)
            if user_currency.is_zero(line_amount):
                continue
            for partial_line in line.matched_debit_ids:
                if partial_line.max_date <= datetime.strptime(
                        date_from, "%Y-%m-%d").date():
                    line_amount += ResCurrency._compute(
                        partial_line.company_id.currency_id, user_currency,
                        partial_line.amount)
            for partial_line in line.matched_credit_ids:
                if partial_line.max_date <= datetime.strptime(
                        date_from, "%Y-%m-%d").date():
                    line_amount -= ResCurrency._compute(
                        partial_line.company_id.currency_id, user_currency,
                        partial_line.amount)
            if not self.env.user.company_id.currency_id.is_zero(line_amount):
                undue_amounts[partner_id] += line_amount
                lines[partner_id].append({
                    'line': line,
                    'amount': line_amount,
                    'period': 6,
                })
        history = []
        for i in range(5):
            args_list = (
                tuple(move_state),
                tuple(account_type),
                tuple(partner_ids),
            )
            dates_query = '(COALESCE(l.date_maturity,l.date)'

            if periods[str(i)]['start'] and periods[str(i)]['stop']:
                dates_query += ' BETWEEN %s AND %s)'
                args_list += (periods[str(i)]['start'],
                              periods[str(i)]['stop'])
            elif periods[str(i)]['start']:
                dates_query += ' >= %s)'
                args_list += (periods[str(i)]['start'], )
            else:
                dates_query += ' <= %s)'
                args_list += (periods[str(i)]['stop'], )
            args_list += (date_from, tuple(company_ids))

            query = '''SELECT l.id
                    FROM account_move_line AS l, account_account, account_move am
                    WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
                        AND (am.state IN %s)
                        AND (account_account.internal_type IN %s)
                        AND ((l.partner_id IN %s) OR (l.partner_id IS NULL))
                        AND ''' + dates_query + '''
                    AND (l.date <= %s)
                    AND l.company_id IN %s'''
            cr.execute(query, args_list)
            partners_amount = {}
            aml_ids = cr.fetchall()
            aml_ids = aml_ids and [x[0] for x in aml_ids] or []
            for line in self.env['account.move.line'].browse(
                    aml_ids).with_context(prefetch_fields=False):
                partner_id = line.partner_id.id or False
                if partner_id not in partners_amount:
                    partners_amount[partner_id] = 0.0
                line_amount = ResCurrency._compute(line.company_id.currency_id,
                                                   user_currency, line.balance)
                if user_currency.is_zero(line_amount):
                    continue
                for partial_line in line.matched_debit_ids:
                    if partial_line.max_date <= datetime.strptime(
                            date_from, "%Y-%m-%d").date():
                        line_amount += ResCurrency._compute(
                            partial_line.company_id.currency_id, user_currency,
                            partial_line.amount)
                for partial_line in line.matched_credit_ids:
                    if partial_line.max_date <= datetime.strptime(
                            date_from, "%Y-%m-%d").date():
                        line_amount -= ResCurrency._compute(
                            partial_line.company_id.currency_id, user_currency,
                            partial_line.amount)

                if not self.env.user.company_id.currency_id.is_zero(
                        line_amount):
                    partners_amount[partner_id] += line_amount
                    lines[partner_id].append({
                        'line': line,
                        'amount': line_amount,
                        'period': i + 1,
                    })
            history.append(partners_amount)

        for partner in partners:
            if partner['partner_id'] is None:
                partner['partner_id'] = False
            at_least_one_amount = False
            values = {}
            undue_amt = 0.0
            if partner[
                    'partner_id'] in undue_amounts:  # Making sure this partner actually was found by the query
                undue_amt = undue_amounts[partner['partner_id']]

            total[6] = total[6] + undue_amt
            values['direction'] = undue_amt
            if not float_is_zero(values['direction'],
                                 precision_rounding=self.env.user.company_id.
                                 currency_id.rounding):
                at_least_one_amount = True

            for i in range(5):
                during = False
                if partner['partner_id'] in history[i]:
                    during = [history[i][partner['partner_id']]]
                # Adding counter
                total[(i)] = total[(i)] + (during and during[0] or 0)
                values[str(i)] = during and during[0] or 0.0
                if not float_is_zero(values[str(i)],
                                     precision_rounding=self.env.user.
                                     company_id.currency_id.rounding):
                    at_least_one_amount = True
            values['total'] = sum([values['direction']] +
                                  [values[str(i)] for i in range(5)])
            ## Add for total
            total[(i + 1)] += values['total']
            values['partner_id'] = partner['partner_id']
            if partner['partner_id']:
                browsed_partner = self.env['res.partner'].browse(
                    partner['partner_id'])
                values['name'] = browsed_partner.name and len(
                    browsed_partner.name) >= 45 and browsed_partner.name[
                        0:40] + '...' or browsed_partner.name
                values['trust'] = browsed_partner.trust
            else:
                values['name'] = _('Unknown Partner')
                values['trust'] = False

            if at_least_one_amount or (
                    self._context.get('include_nullified_amount')
                    and lines[partner['partner_id']]):
                res.append(values)
        return res, total, lines
Exemplo n.º 59
0
    def _run_fifo(self, quantity, company):
        self.ensure_one()

        # Find back incoming stock valuation layers (called candidates here) to value `quantity`.
        qty_to_take_on_candidates = quantity
        candidates = self.env['stock.valuation.layer'].sudo().search([
            ('product_id', '=', self.id),
            ('remaining_qty', '>', 0),
            ('company_id', '=', company.id),
        ])
        new_standard_price = 0
        tmp_value = 0  # to accumulate the value taken on the candidates
        for candidate in candidates:
            qty_taken_on_candidate = min(qty_to_take_on_candidates,
                                         candidate.remaining_qty)

            candidate_unit_cost = candidate.remaining_value / candidate.remaining_qty
            new_standard_price = candidate_unit_cost
            value_taken_on_candidate = qty_taken_on_candidate * candidate_unit_cost
            value_taken_on_candidate = candidate.currency_id.round(
                value_taken_on_candidate)
            new_remaining_value = candidate.remaining_value - value_taken_on_candidate

            candidate_vals = {
                'remaining_qty':
                candidate.remaining_qty - qty_taken_on_candidate,
                'remaining_value': new_remaining_value,
            }

            candidate.write(candidate_vals)

            qty_to_take_on_candidates -= qty_taken_on_candidate
            tmp_value += value_taken_on_candidate

            if float_is_zero(qty_to_take_on_candidates,
                             precision_rounding=self.uom_id.rounding):
                if float_is_zero(candidate.remaining_qty,
                                 precision_rounding=self.uom_id.rounding):
                    next_candidates = candidates.filtered(
                        lambda svl: svl.remaining_qty > 0)
                    new_standard_price = next_candidates and next_candidates[
                        0].unit_cost or new_standard_price
                break

        # Update the standard price with the price of the last used candidate, if any.
        if new_standard_price and self.cost_method == 'fifo':
            self.sudo().with_company(company.id).with_context(
                disable_auto_svl=True).standard_price = new_standard_price

        # If there's still quantity to value but we're out of candidates, we fall in the
        # negative stock use case. We chose to value the out move at the price of the
        # last out and a correction entry will be made once `_fifo_vacuum` is called.
        vals = {}
        if float_is_zero(qty_to_take_on_candidates,
                         precision_rounding=self.uom_id.rounding):
            vals = {
                'value': -tmp_value,
                'unit_cost': tmp_value / quantity,
            }
        else:
            assert qty_to_take_on_candidates > 0
            last_fifo_price = new_standard_price or self.standard_price
            negative_stock_value = last_fifo_price * -qty_to_take_on_candidates
            tmp_value += abs(negative_stock_value)
            vals = {
                'remaining_qty': -qty_to_take_on_candidates,
                'value': -tmp_value,
                'unit_cost': last_fifo_price,
            }
        return vals
Exemplo n.º 60
0
    def test_timesheet_order(self):
        """ Test timesheet invoicing with 'invoice on order' timetracked products
                1. create SO with 2 ordered product and confirm
                2. create invoice
                3. log timesheet
                4. add new SO line (ordered service)
                5. create new invoice
        """
        # create SO and confirm it
        sale_order = self.env['sale.order'].create({
            'partner_id':
            self.partner_a.id,
            'partner_invoice_id':
            self.partner_a.id,
            'partner_shipping_id':
            self.partner_a.id,
            'pricelist_id':
            self.company_data['default_pricelist'].id,
        })
        so_line_ordered_project_only = self.env['sale.order.line'].create({
            'name':
            self.product_order_timesheet4.name,
            'product_id':
            self.product_order_timesheet4.id,
            'product_uom_qty':
            10,
            'product_uom':
            self.product_order_timesheet4.uom_id.id,
            'price_unit':
            self.product_order_timesheet4.list_price,
            'order_id':
            sale_order.id,
        })
        so_line_ordered_global_project = self.env['sale.order.line'].create({
            'name':
            self.product_order_timesheet2.name,
            'product_id':
            self.product_order_timesheet2.id,
            'product_uom_qty':
            50,
            'product_uom':
            self.product_order_timesheet2.uom_id.id,
            'price_unit':
            self.product_order_timesheet2.list_price,
            'order_id':
            sale_order.id,
        })
        so_line_ordered_project_only.product_id_change()
        so_line_ordered_global_project.product_id_change()
        sale_order.action_confirm()
        task_serv2 = self.env['project.task'].search([
            ('sale_line_id', '=', so_line_ordered_global_project.id)
        ])
        project_serv1 = self.env['project.project'].search([
            ('sale_line_id', '=', so_line_ordered_project_only.id)
        ])

        self.assertEqual(
            sale_order.tasks_count, 1,
            "One task should have been created on SO confirmation")
        self.assertEqual(
            len(sale_order.project_ids), 2,
            "One project should have been created by the SO, when confirmed + the one from SO line 2 'task in global project'"
        )
        self.assertEqual(
            sale_order.analytic_account_id, project_serv1.analytic_account_id,
            "The created project should be linked to the analytic account of the SO"
        )

        # create invoice
        invoice1 = sale_order._create_invoices()[0]

        # let's log some timesheets (on the project created by so_line_ordered_project_only)
        timesheet1 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            task_serv2.project_id.id,
            'task_id':
            task_serv2.id,
            'unit_amount':
            10.5,
            'employee_id':
            self.employee_user.id,
        })
        self.assertEqual(
            so_line_ordered_global_project.qty_delivered, 10.5,
            'Timesheet directly on project does not increase delivered quantity on so line'
        )
        self.assertEqual(
            sale_order.invoice_status, 'invoiced',
            'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so'
        )
        self.assertEqual(
            timesheet1.timesheet_invoice_type, 'billable_fixed',
            "Timesheets linked to SO line with ordered product shoulbe be billable fixed"
        )
        self.assertFalse(
            timesheet1.timesheet_invoice_id,
            "The timesheet1 should not be linked to the invoice, since we are in ordered quantity"
        )

        timesheet2 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            task_serv2.project_id.id,
            'task_id':
            task_serv2.id,
            'unit_amount':
            39.5,
            'employee_id':
            self.employee_user.id,
        })
        self.assertEqual(
            so_line_ordered_global_project.qty_delivered, 50,
            'Sale Timesheet: timesheet does not increase delivered quantity on so line'
        )
        self.assertEqual(
            sale_order.invoice_status, 'invoiced',
            'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so'
        )
        self.assertEqual(
            timesheet2.timesheet_invoice_type, 'billable_fixed',
            "Timesheets linked to SO line with ordered product shoulbe be billable fixed"
        )
        self.assertFalse(
            timesheet2.timesheet_invoice_id,
            "The timesheet should not be linked to the invoice, since we are in ordered quantity"
        )

        timesheet3 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            task_serv2.project_id.id,
            'unit_amount':
            10,
            'employee_id':
            self.employee_user.id,
        })
        self.assertEqual(
            so_line_ordered_project_only.qty_delivered, 0.0,
            'Timesheet directly on project does not increase delivered quantity on so line'
        )
        self.assertEqual(
            timesheet3.timesheet_invoice_type, 'non_billable_project',
            "Timesheets without task shoulbe be 'no project found'")
        self.assertFalse(
            timesheet3.timesheet_invoice_id,
            "The timesheet should not be linked to the invoice, since we are in ordered quantity"
        )

        # log timesheet on task in global project (higher than the initial ordrered qty)
        timesheet4 = self.env['account.analytic.line'].create({
            'name':
            'Test Line',
            'project_id':
            task_serv2.project_id.id,
            'task_id':
            task_serv2.id,
            'unit_amount':
            5,
            'employee_id':
            self.employee_user.id,
        })
        self.assertEqual(
            sale_order.invoice_status, 'upselling',
            'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so'
        )
        self.assertFalse(
            timesheet4.timesheet_invoice_id,
            "The timesheet should not be linked to the invoice, since we are in ordered quantity"
        )

        # add so line with produdct "create task in new project".
        so_line_ordered_task_in_project = self.env['sale.order.line'].create({
            'name':
            self.product_order_timesheet3.name,
            'product_id':
            self.product_order_timesheet3.id,
            'product_uom_qty':
            3,
            'product_uom':
            self.product_order_timesheet3.uom_id.id,
            'price_unit':
            self.product_order_timesheet3.list_price,
            'order_id':
            sale_order.id,
        })

        self.assertEqual(
            sale_order.invoice_status, 'to invoice',
            'Sale Timesheet: Adding a new service line (so line) should put the SO in "to invocie" state.'
        )
        self.assertEqual(
            sale_order.tasks_count, 2,
            "Two tasks (1 per SO line) should have been created on SO confirmation"
        )
        self.assertEqual(
            len(sale_order.project_ids), 2,
            "No new project should have been created by the SO, when selling 'new task in new project' product, since it reuse the one from 'project only'."
        )

        # get first invoice line of sale line linked to timesheet1
        invoice_line_1 = so_line_ordered_global_project.invoice_lines.filtered(
            lambda line: line.move_id == invoice1)

        self.assertEqual(
            so_line_ordered_global_project.product_uom_qty,
            invoice_line_1.quantity,
            "The invoice (ordered) quantity should not change when creating timesheet"
        )

        # timesheet can be modified
        timesheet1.write({'unit_amount': 12})

        self.assertEqual(
            so_line_ordered_global_project.product_uom_qty,
            invoice_line_1.quantity,
            "The invoice (ordered) quantity should not change when modifying timesheet"
        )

        # create second invoice
        invoice2 = sale_order._create_invoices()[0]

        self.assertEqual(
            len(sale_order.invoice_ids), 2,
            "A second invoice should have been created from the SO")
        self.assertTrue(
            float_is_zero(invoice2.amount_total -
                          so_line_ordered_task_in_project.price_unit * 3,
                          precision_digits=2),
            'Sale: invoice generation on timesheets product is wrong')

        self.assertFalse(
            timesheet1.timesheet_invoice_id,
            "The timesheet1 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet2.timesheet_invoice_id,
            "The timesheet2 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet3.timesheet_invoice_id,
            "The timesheet3 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet4.timesheet_invoice_id,
            "The timesheet4 should not be linked to the invoice, since we are in ordered quantity"
        )

        # validate the first invoice
        invoice1.action_post()

        self.assertEqual(
            so_line_ordered_global_project.product_uom_qty,
            invoice_line_1.quantity,
            "The invoice (ordered) quantity should not change when modifying timesheet"
        )
        self.assertFalse(
            timesheet1.timesheet_invoice_id,
            "The timesheet1 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet2.timesheet_invoice_id,
            "The timesheet2 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet3.timesheet_invoice_id,
            "The timesheet3 should not be linked to the invoice, since we are in ordered quantity"
        )
        self.assertFalse(
            timesheet4.timesheet_invoice_id,
            "The timesheet4 should not be linked to the invoice, since we are in ordered quantity"
        )

        # timesheet can still be modified
        timesheet1.write({'unit_amount': 13})