Ejemplo n.º 1
0
class Statement(dd.Model):
    class Meta:
        app_label = 'b2c'
        abstract = dd.is_abstract_model(__name__, 'Statement')
        verbose_name = _("Statement")
        verbose_name_plural = _("Statements")

    def __str__(self):
        return self.statement_number

    account = dd.ForeignKey('b2c.Account')
    statement_number = models.CharField(
        _('Statement number'),
        blank=False,
        max_length=10,
        help_text=_("Combination of the year and the legal sequential number"
                    " of the paper statement."))

    start_date = models.DateField(_('Start date'), null=True)
    end_date = models.DateField(_('End date'), null=True)
    # date_done = models.DateTimeField(_('Import Date'), null=True)
    balance_start = dd.PriceField(_("Initial amount"), null=True)
    balance_end = dd.PriceField(_("Final amount"), null=True)
    # balance_end_real = dd.PriceField(_("Real end balance"), null=True)
    local_currency = models.CharField(_('Currency'), max_length=3)
Ejemplo n.º 2
0
class BankStatement(DatedFinancialVoucher):
    class Meta:
        app_label = 'finan'
        abstract = dd.is_abstract_model(__name__, 'BankStatement')
        verbose_name = _("Bank Statement")
        verbose_name_plural = _("Bank Statements")

    balance1 = dd.PriceField(_("Old balance"), default=ZERO)
    balance2 = dd.PriceField(_("New balance"), default=ZERO, blank=True)

    # show_items = dd.ShowSlaveTable('finan.ItemsByBankStatement')

    def get_previous_voucher(self):
        if not self.journal_id:
            #~ logger.info("20131005 no journal")
            return None
        qs = self.__class__.objects.filter(
            journal=self.journal).order_by('-entry_date')
        if qs.count() > 0:
            #~ logger.info("20131005 no other vouchers")
            return qs[0]

    def on_create(self, ar):
        super(BankStatement, self).on_create(ar)
        if self.balance1 == ZERO:
            prev = self.get_previous_voucher()
            if prev is not None:
                #~ logger.info("20131005 prev is %s",prev)
                self.balance1 = prev.balance2

    def on_duplicate(self, ar, master):
        self.balance1 = self.balance2 = ZERO
        super(BankStatement, self).on_duplicate(ar, master)

    def get_wanted_movements(self):
        """Implements
        :meth:`lino_xl.lib.ledger.Voucher.get_wanted_movements`
        for bank statements.

        As a side effect this also computes the :attr:`balance1` and
        :attr:`balance2` fields and saves the voucher.

        """
        # dd.logger.info("20151211 cosi.BankStatement.get_wanted_movements()")
        a = self.journal.account
        if not a:
            warn_jnl_account(self.journal)
        amount, movements_and_items = self.get_finan_movements()
        self.balance2 = self.balance1 + amount
        if abs(self.balance2) > MAX_AMOUNT:
            dd.logger.warning("Oops, %s is too big", self.balance2)
            return
        for m, i in movements_and_items:
            yield m
        yield self.create_movement(
            None, (a, None), None, self.journal.dc, amount)
        # side effect!:
        self.full_clean()
        self.save()
Ejemplo n.º 3
0
 def addann(kw, name, dc, flt):
     mvts = rt.models.ledger.Movement.objects.filter(dc=dc, **flt)
     mvts = mvts.order_by()
     mvts = mvts.values(outer_link)  # this was the important thing
     mvts = mvts.annotate(
         total=Sum('amount', output_field=dd.PriceField()))
     mvts = mvts.values('total')
     kw[name] = Subquery(mvts, output_field=dd.PriceField())
Ejemplo n.º 4
0
 def addann(self, kw, name, dc, flt):
     mvts = rt.models.ledger.Movement.objects.filter(dc=dc, **flt)
     mvts = mvts.order_by()
     mvts = mvts.values(self.outer_link)  # this was the important thing
     mvts = mvts.annotate(total=Sum(
         'amount', output_field=dd.PriceField(decimal_places=14)))# For Django2 we need to set decimal_places to 14 which is the max of decimal_places used in ReportEntry fields.
     mvts = mvts.values('total')
     kw[name] = Subquery(mvts, output_field=dd.PriceField(decimal_places=14))
Ejemplo n.º 5
0
class BankStatement(DatedFinancialVoucher):
    """A **bank statement** is a document issued by the bank, which
    reports all transactions which occured on a given account during a
    given period.

    .. attribute:: balance1

        The old (or start) balance.

    .. attribute:: balance2

        The new (or end) balance.

    """
    class Meta:
        app_label = 'finan'
        abstract = dd.is_abstract_model(__name__, 'BankStatement')
        verbose_name = _("Bank Statement")
        verbose_name_plural = _("Bank Statements")

    balance1 = dd.PriceField(_("Old balance"), default=ZERO)
    balance2 = dd.PriceField(_("New balance"), default=ZERO, blank=True)

    def get_previous_voucher(self):
        if not self.journal_id:
            #~ logger.info("20131005 no journal")
            return None
        qs = self.__class__.objects.filter(
            journal=self.journal).order_by('-voucher_date')
        if qs.count() > 0:
            #~ logger.info("20131005 no other vouchers")
            return qs[0]

    def on_create(self, ar):
        super(BankStatement, self).on_create(ar)
        if self.balance1 == ZERO:
            prev = self.get_previous_voucher()
            if prev is not None:
                #~ logger.info("20131005 prev is %s",prev)
                self.balance1 = prev.balance2

    def get_wanted_movements(self):
        # dd.logger.info("20151211 cosi.BankStatement.get_wanted_movements()")
        a = self.journal.account
        if not a:
            warn_jnl_account(self.journal)
        amount, movements_and_items = self.get_finan_movements()
        self.balance2 = self.balance1 + amount
        for m, i in movements_and_items:
            yield m
        yield self.create_movement(None, a, None, self.journal.dc, amount)
Ejemplo n.º 6
0
class QtyVatItemBase(VatItemBase):
    """Model mixin for items of a :class:`VatTotal`, adds `unit_price` and
`qty`.

    Abstract Base class for :class:`lino_cosi.lib.sales.InvoiceItem` and
    :class:`lino_cosi.lib.sales.OrderItem`, i.e. the lines of invoices
    *with* unit prices and quantities.

    """
    class Meta:
        abstract = True

    unit_price = dd.PriceField(_("Unit price"), blank=True, null=True)
    qty = dd.QuantityField(_("Quantity"), blank=True, null=True)

    def unit_price_changed(self, ar):
        self.reset_totals(ar)

    def qty_changed(self, ar):
        self.reset_totals(ar)

    def reset_totals(self, ar):
        super(QtyVatItemBase, self).reset_totals(ar)
        # if not self.voucher.auto_compute_totals:
        #     if self.qty:
        #         if self.voucher.item_vat:
        #             self.unit_price = self.total_incl / self.qty
        #         else:
        #             self.unit_price = self.total_base / self.qty

        if self.unit_price is not None and self.qty is not None:
            self.set_amount(ar, myround(self.unit_price * self.qty))
Ejemplo n.º 7
0
def inject_tradetype_fields(sender, **kw):
    """This defines certain database fields related to your
    :class:`TradeTypes`.

    """
    # print(20200622, list([i.invoice_account_field_name for i in TradeTypes.items()]))
    for tt in TradeTypes.items():
        if tt.invoice_account_field_name is not None:
            dd.inject_field(
                'contacts.Partner', tt.invoice_account_field_name,
                dd.ForeignKey('ledger.Account',
                              verbose_name=tt.invoice_account_field_label,
                              on_delete=models.PROTECT,
                              related_name='partners_by_' +
                              tt.invoice_account_field_name,
                              blank=True,
                              null=True))
        if tt.base_account_field_name is not None:
            dd.inject_field(
                'products.Product', tt.base_account_field_name,
                dd.ForeignKey('ledger.Account',
                              verbose_name=tt.base_account_field_label,
                              on_delete=models.PROTECT,
                              related_name='products_by_' +
                              tt.base_account_field_name,
                              blank=True,
                              null=True))
        if tt.price_field_name is not None:
            dd.inject_field(
                'products.Product', tt.price_field_name,
                dd.PriceField(verbose_name=tt.price_field_label,
                              blank=True,
                              null=True))
Ejemplo n.º 8
0
class Product(mixins.BabelNamed):

    price = dd.PriceField(_("Price"), blank=True, null=True)

    category = Categories.field(blank=True, null=True)

    class Meta:
        verbose_name = 'Product'
        verbose_name_plural = 'Products'
Ejemplo n.º 9
0
class InvoiceItem(AccountVoucherItem, ProjectRelated):
    """An item of an :class:`AccountInvoice`."""
    class Meta:
        verbose_name = _("Invoice item")
        verbose_name_plural = _("Invoice items")

    voucher = dd.ForeignKey('vatless.AccountInvoice', related_name='items')
    title = models.CharField(_("Description"), max_length=200, blank=True)
    amount = dd.PriceField(_("Amount"), blank=True, null=True)
Ejemplo n.º 10
0
class VatProductInvoice(SalesDocument, Matching):
    class Meta:
        app_label = 'sales'
        abstract = dd.is_abstract_model(__name__, 'VatProductInvoice')
        verbose_name = _("Sales invoice")
        verbose_name_plural = _("Sales invoices")

    quick_search_fields = "partner__name subject"

    # show_items = dd.ShowSlaveTable('sales.ItemsByInvoice')

    # make_copy = MakeCopy()

    @classmethod
    def get_registrable_fields(cls, site):
        for f in super(VatProductInvoice, cls).get_registrable_fields(site):
            yield f
        yield 'due_date'
        # yield 'order'

        yield 'voucher_date'
        yield 'entry_date'
        yield 'user'
        # yield 'item_vat'

    def get_print_items(self, ar):
        return self.print_items_table.request(self)

    @dd.virtualfield(dd.PriceField(_("Balance to pay")))
    def balance_to_pay(self, ar):
        Movement = rt.models.ledger.Movement
        qs = Movement.objects.filter(partner=self.get_partner(),
                                     cleared=False,
                                     match=self.get_match())
        return Movement.get_balance(not self.journal.dc, qs)

    @dd.virtualfield(dd.PriceField(_("Balance before")))
    def balance_before(self, ar):
        Movement = rt.models.ledger.Movement
        qs = Movement.objects.filter(partner=self.get_partner(),
                                     cleared=False,
                                     value_date__lte=self.entry_date)
        qs = qs.exclude(voucher=self)
        return Movement.get_balance(not self.journal.dc, qs)
Ejemplo n.º 11
0
class ReportEntry(dd.Model):
    class Meta:
        abstract = True

    show_in_site_search = False
    allow_cascaded_delete = ['report']

    report = dd.ForeignKey('sheets.Report')
    old_d = dd.PriceField(_("Debit before"), 14, null=True, blank=True)
    old_c = dd.PriceField(_("Credit before"), 14, null=True, blank=True)
    during_d = dd.PriceField(_("Debit"), 14, null=True, blank=True)
    during_c = dd.PriceField(_("Credit"), 14, null=True, blank=True)

    def new_balance(self):
        return Balance(self.old_d, self.old_c) + Balance(
            self.during_d, self.during_c)

    def value2html(self, ar):
        txt = dd.format_currency(self.value, False, True)
        if self.item.is_heading():
            # return E.b(txt)
            return E.div(E.b(txt), align="right")
        # return txt
        return E.div(txt, align="right")

    @dd.virtualfield(dd.PriceField(_("Debit after")))
    def new_d(self, ar):
        return self.new_balance().d

    @dd.virtualfield(dd.PriceField(_("Credit after")))
    def new_c(self, ar):
        return self.new_balance().c
Ejemplo n.º 12
0
class Statement(dd.Model):
    """A bank statement.

    This data is automaticaly imported by :class:`ImportStatements`.

    .. attribute:: sequence_number

        The legal sequential number of the statement, as assigned by
        the bank.

        See `LegalSequenceNumber
        <https://www.iso20022.org/standardsrepository/public/wqt/Content/mx/camt.053.001.02#mx/camt.053.001.02/Statement/LegalSequenceNumber>`_
        (`<LglSeqNb>`) for details.

    """
    class Meta:
        app_label = 'b2c'
        abstract = dd.is_abstract_model(__name__, 'Statement')
        verbose_name = _("Statement")
        verbose_name_plural = _("Statements")

    def __str__(self):
        return self.statement_number

    account = dd.ForeignKey('b2c.Account')
    statement_number = models.CharField(
        _('Statement number'),
        blank=False,
        max_length=10,
        help_text=_("Combination of the year and the legal sequential number"
                    " of the paper statement."))

    start_date = models.DateField(_('Start date'), null=True)
    end_date = models.DateField(_('End date'), null=True)
    # date_done = models.DateTimeField(_('Import Date'), null=True)
    balance_start = dd.PriceField(_("Initial amount"), null=True)
    balance_end = dd.PriceField(_("Final amount"), null=True)
    # balance_end_real = dd.PriceField(_("Real end balance"), null=True)
    local_currency = models.CharField(_('Currency'), max_length=3)
Ejemplo n.º 13
0
class PaymentOrder(FinancialVoucher):
    """A **payment order** is when a user instructs a bank to execute a
    series of outgoing transactions from a given bank account.

    """
    class Meta:
        app_label = 'finan'
        abstract = dd.is_abstract_model(__name__, 'PaymentOrder')
        verbose_name = _("Payment Order")
        verbose_name_plural = _("Payment Orders")

    total = dd.PriceField(_("Total"), blank=True, null=True)
    execution_date = models.DateField(_("Execution date"),
                                      blank=True,
                                      null=True)

    def get_wanted_movements(self):
        """Implements
        :meth:`lino_cosi.lib.ledger.models.Voucher.get_wanted_movements`
        for payment orders.

        The generated movements

        """
        # dd.logger.info("20151211 cosi.PaymentOrder.get_wanted_movements()")
        acc = self.journal.account
        if not acc:
            warn_jnl_account(self.journal)
        amount, movements_and_items = self.get_finan_movements()
        self.total = -amount
        for m, i in movements_and_items:
            yield m
            if acc.needs_partner:
                yield self.create_movement(i,
                                           acc,
                                           m.project,
                                           not m.dc,
                                           m.amount,
                                           partner=m.partner,
                                           match=m.match
                                           or m.get_default_match())
        if not acc.needs_partner:
            yield self.create_movement(None, acc, None, not self.journal.dc,
                                       amount)

    def add_item_from_due(self, obj, **kwargs):
        # if obj.bank_account is None:
        #     return
        i = super(PaymentOrder, self).add_item_from_due(obj, **kwargs)
        i.bank_account = obj.bank_account
        return i
Ejemplo n.º 14
0
class ShippingMode(mixins.BabelNamed):
    """
    Represents a possible method of how the items described in a
    :class:`SalesDocument` are to be transferred from us to our customer.

    .. attribute:: price

    """
    class Meta:
        app_label = 'delivery'
        verbose_name = _("Shipping Mode")
        verbose_name_plural = _("Shipping Modes")

    price = dd.PriceField(blank=True, null=True)
Ejemplo n.º 15
0
class Product(dd.Model):
    product_name = models.CharField("Product ", max_length=25,null=True , blank=True)
    product_type = models.CharField("Product Type", max_length=25, null=True, blank=True)
    #product_price= models.DecimalField("Price", decimal_places=2, max_digits=10)
    #product_qt=models.IntegerField("Quantity")
    product_qt = dd.QuantityField()#modified with Hand only
    last_qt = dd.QuantityField(default='0')
    rest_qt = dd.QuantityField()
    #new_qt = dd.QuantityField()
    new_qt = models.BooleanField(default=False)
    product_price = dd.PriceField()

    def __str__(self):
        #not work self.product_qt = self.product_qt - Delivery.qty
        return "%s ,%s EUR |*%s EUR*" % (self.product_name, self.product_price, self.product_qt * self.product_price)
Ejemplo n.º 16
0
def inject_tradetype_fields(sender, **kw):
    """This defines certain database fields related to your
    :class:`TradeTypes`.

    """
    for tt in TradeTypes.items():
        if tt.partner_account_field_name is not None:
            dd.inject_field(
                'system.SiteConfig', tt.partner_account_field_name,
                dd.ForeignKey('accounts.Account',
                              verbose_name=tt.partner_account_field_label,
                              related_name='configs_by_' +
                              tt.partner_account_field_name,
                              blank=True,
                              null=True))
        if tt.vat_account_field_name is not None:
            dd.inject_field(
                'system.SiteConfig', tt.vat_account_field_name,
                dd.ForeignKey('accounts.Account',
                              verbose_name=tt.vat_account_field_label,
                              related_name='configs_by_' +
                              tt.vat_account_field_name,
                              blank=True,
                              null=True))
        if tt.base_account_field_name is not None:
            dd.inject_field(
                'system.SiteConfig', tt.base_account_field_name,
                dd.ForeignKey('accounts.Account',
                              verbose_name=tt.base_account_field_label,
                              related_name='configs_by_' +
                              tt.base_account_field_name,
                              blank=True,
                              null=True))
            dd.inject_field(
                'products.Product', tt.base_account_field_name,
                dd.ForeignKey('accounts.Account',
                              verbose_name=tt.base_account_field_label,
                              related_name='products_by_' +
                              tt.base_account_field_name,
                              blank=True,
                              null=True))
        if tt.price_field_name is not None:
            dd.inject_field(
                'products.Product', tt.price_field_name,
                dd.PriceField(verbose_name=tt.price_field_label,
                              blank=True,
                              null=True))
Ejemplo n.º 17
0
class Item(dd.Model):
    name = models.CharField(max_length=50)
    qty = dd.QuantityField(default='1')
    discount = dd.QuantityField()
    price = dd.PriceField()

    def __unicode__(self):
        if self.discount is None:
            return "%s x %s at %s = %s EUR" % (self.qty, self.name, self.price,
                                               self.total())
        else:
            return "%s x %s at %s (-%s) = %s EUR" % (
                self.qty, self.name, self.price, self.discount, self.total())

    def total(self):
        if self.discount is None:
            return self.qty * self.price
        else:
            return self.qty * (1 - self.discount) * self.price
Ejemplo n.º 18
0
class FinancialVoucherItem(VoucherItem, SequencedVoucherItem,
                           ProjectRelated, Matching):
    class Meta:
        abstract = True
        verbose_name = _("Item")
        verbose_name_plural = _("Items")
        app_label = "finan"

    amount = dd.PriceField(_("Amount"), default=ZERO, null=False)
    dc = DebitOrCreditField()
    remark = models.CharField(
        _("Your reference"), max_length=200, blank=True)
    account = dd.ForeignKey('ledger.Account', blank=True, null=True)
    partner = dd.ForeignKey('contacts.Partner', blank=True, null=True)

    quick_search_fields = 'remark account__ref account__name partner__name'

    @dd.chooser(simple_values=True)
    def match_choices(cls, voucher, partner):
        return cls.get_match_choices(voucher.journal, partner)

    def add_item_from_due(self, obj, **kwargs):
        return self.voucher.add_item_from_due(obj, **kwargs)

    # def get_default_match(self): # 20191226
    def __str__(self):
        """Used as `match` when no explicit match is specified for
        this movement.

        """
        if self.voucher_id and self.voucher.journal_id:
            return "%s %s:%s" % (
                self.voucher.journal.ref, self.voucher.number, self.seqno)
            # return str(self.date)
        return models.Model.__str__(self)
        # return super(FinancialVoucherItem, self).__str__()

    def get_siblings(self):
        return self.voucher.items.all()

    def match_changed(self, ar):

        if not self.match or not self.voucher.journal.auto_fill_suggestions:
            return

        flt = dict(match=self.match, cleared=False)
        if not dd.plugins.finan.suggest_future_vouchers:
            flt.update(value_date__lte=self.voucher.entry_date)

        self.collect_suggestions(ar, flt)


    def partner_changed(self, ar):
        """The :meth:`trigger method <lino.core.model.Model.FOO_changed>` for
        :attr:`partner`.

        """
        # dd.logger.info("20160329 FinancialMixin.partner_changed")
        if not self.partner or not self.voucher.journal.auto_fill_suggestions:
            return
        flt = dict(partner=self.partner, cleared=False)

        if not dd.plugins.finan.suggest_future_vouchers:
            flt.update(value_date__lte=self.voucher.entry_date)

        if self.match:
            flt.update(match=self.match)

        self.collect_suggestions(ar, models.Q(**flt))

    def collect_suggestions(self, ar, flt):
        suggestions = list(ledger.get_due_movements(
            self.voucher.journal.dc, flt))

        if len(suggestions) == 0:
            self.match = ""
        elif len(suggestions) == 1:
            self.fill_suggestion(suggestions[0])
        elif ar:
            self.match = _("{} suggestions").format(len(suggestions))

            # def ok(ar2):
            #     # self.fill_suggestion(suggestions[0])
            #     # self.set_grouper(suggestions)
            #     ar2.error(_("Oops, not implemented."))
            #     return

            # elems = ["Cool! ", E.b(str(self.partner)),
            #          " has ", E.b(str(len(suggestions))),
            #          " suggestions! Click "]
            # ba = ar.actor.get_action_by_name('suggest')
            # elems.append(ar.action_button(ba, self))
            # elems.append(".")
            # html = E.p(*elems)
            # # dd.logger.info("20160526 %s", E.tostring(html))
            # ar.success(E.tostring(html), alert=True)
            # # ar.confirm(ok, E.tostring(html))

    def account_changed(self, ar):
        if not self.account:
            return
        if self.account.default_amount:
            self.amount = self.account.default_amount
            self.dc = not self.voucher.journal.dc
            # self.dc = not self.account.type.dc
            return
        if self.voucher.auto_compute_amount:
            total = Decimal()
            dc = self.voucher.journal.dc
            for item in self.voucher.items.exclude(id=self.id):
                if item.dc == dc:
                    total -= item.amount
                else:
                    total += item.amount
            if total < 0:
                dc = not dc
                total = - total
            self.dc = dc
            self.amount = total


    def get_partner(self):
        return self.partner or self.project

    def fill_suggestion(self, match):
        """Fill the fields of this item from the given suggestion (a
        `DueMovement` instance).

        """
        # if not match.balance:
        #     raise Exception("20151117")
        if match.trade_type:
            self.account = match.trade_type.get_main_account()
        if self.account_id is None:
            self.account = match.account
        self.dc = match.dc
        self.amount = - match.balance
        self.match = match.match
        self.project = match.project
        self.partner = match.partner  # when called from match_changed()

    def guess_amount(self):
        self.account_changed(None)
        if self.amount is not None:
            return
        self.partner_changed(None)
        if self.amount is not None:
            return
        self.amount = ZERO

    def full_clean(self, *args, **kwargs):
        # if self.amount is None:
        #     if self.account and self.account.default_amount:
        #         self.amount = self.account.default_amount
        #         self.dc = self.account.type.dc
        #     else:
        #         self.amount = ZERO
        if self.dc is None:
            self.dc = not self.voucher.journal.dc
            # if self.account is None:
            #     self.dc = not self.voucher.journal.dc
            # else:
            #     self.dc = not self.account.type.dc
        if self.amount is None:
            # amount can be None e.g. if user entered ''
            self.guess_amount()
        elif self.amount < 0:
            self.amount = - self.amount
            self.dc = not self.dc

        if False: # temporarily deactivated for data migration
            problems = list(FinancialVoucherItemChecker.check_instance(self))
            if len(problems):
                raise ValidationError("20181120 {}".format(
                    '\n'.join([p[1] for p in problems])))

        # dd.logger.info("20151117 FinancialVoucherItem.full_clean a %s", self.amount)
        super(FinancialVoucherItem, self).full_clean(*args, **kwargs)
Ejemplo n.º 19
0
class Item(dd.Model):
    class Meta:
        app_label = 'invoicing'
        abstract = dd.is_abstract_model(__name__, 'Item')
        verbose_name = _("Invoicing suggestion")
        verbose_name_plural = _("Invoicing suggestions")

    plan = dd.ForeignKey('invoicing.Plan', related_name="items")
    partner = dd.ForeignKey('contacts.Partner')

    generator_type = dd.ForeignKey(ContentType,
                                   editable=True,
                                   blank=True,
                                   null=True,
                                   verbose_name=format_lazy(
                                       u"{} {}", generator_label, _('(type)')))
    generator_id = GenericForeignKeyIdField(generator_type,
                                            editable=True,
                                            blank=True,
                                            null=True,
                                            verbose_name=format_lazy(
                                                u"{} {}", generator_label,
                                                _('(object)')))
    generator = GenericForeignKey('generator_type',
                                  'generator_id',
                                  verbose_name=generator_label)

    # first_date = models.DateField(_("First date"))
    # last_date = models.DateField(_("Last date"))
    amount = dd.PriceField(_("Amount"), default=ZERO)
    # number_of_invoiceables = models.IntegerField(_("Number"), default=0)
    preview = models.TextField(_("Preview"), blank=True)
    selected = models.BooleanField(_("Selected"), default=True)
    invoice = dd.ForeignKey(dd.plugins.invoicing.voucher_model,
                            verbose_name=_("Invoice"),
                            null=True,
                            blank=True,
                            on_delete=models.SET_NULL)

    exec_item = ExecuteItem()

    @dd.displayfield(_("Invoice"))
    def invoice_button(self, ar):
        if ar is not None:
            if self.invoice_id:
                return self.invoice.obj2href(ar)
            ba = ar.actor.get_action_by_name('exec_item')
            if ar.actor.get_row_permission(self, ar, None, ba):
                return ar.action_button(ba, self)
        return ''

    def create_invoice(self, ar):
        if self.plan.area_id is None:
            raise Warning(_("No area specified"))
        if self.plan.area.journal is None:
            raise Warning(
                _("No journal configured for {}").format(self.plan.area))
        invoice = self.plan.create_invoice(partner=self.partner,
                                           user=ar.get_user())
        lng = invoice.get_print_language()
        items = []
        max_date = self.plan.get_max_date()
        with translation.override(lng):
            if self.generator:
                generators = [self.generator]
            else:
                generators = [
                    ig
                    for ig in self.plan.get_generators_for_plan(self.partner)
                    if ig.allow_group_invoices()
                ]
            for ig in generators:
                info = ig.compute_invoicing_info(max_date)
                pt = ig.get_invoiceable_payment_term()
                if pt:
                    invoice.payment_term = pt
                pt = ig.get_invoiceable_paper_type()
                if pt:
                    invoice.paper_type = pt

                # for i in ig.get_invoice_items(info, ITEM_MODEL, ar):
                for i in ig.get_invoice_items(info, invoice, ar):
                    # kwargs.update(voucher=invoice)
                    # i = ITEM_MODEL(**kwargs)
                    # if 'amount' in kwargs:
                    #     i.set_amount(ar, kwargs['amount'])
                    # amount = kwargs.get('amount', ZERO)
                    # if amount:
                    #     i.set_amount(ar, amount)
                    items.append((ig, i))

        if len(items) == 0:
            # neither invoice nor items are saved
            raise Warning(_("No invoiceables found for %s.") % self)
            # dd.logger.warning(
            #     _("No invoiceables found for %s.") % self.partner)
            # return

        invoice.full_clean()
        invoice.save()

        for ig, i in items:
            # assign voucher after it has been saved
            i.voucher = invoice
            ig.setup_invoice_item(i)
            # if not i.title:
            #     i.title = ig.get_invoiceable_title(invoice)
            # compute the sales_price and amounts, but don't change
            # title and description

            # title = i.title
            # i.product_changed()
            i.discount_changed()
            # i.title = title
            i.full_clean()
            i.save()

        self.invoice = invoice
        self.full_clean()
        self.save()

        invoice.compute_totals()
        invoice.full_clean()
        invoice.save()
        invoice.register(ar)

        return invoice

    def __str__(self):
        return "{0} {1}".format(self.plan, self.partner)
Ejemplo n.º 20
0
class PaymentOrder(FinancialVoucher, Printable):
    class Meta:
        app_label = 'finan'
        abstract = dd.is_abstract_model(__name__, 'PaymentOrder')
        verbose_name = _("Payment Order")
        verbose_name_plural = _("Payment Orders")

    total = dd.PriceField(_("Total"), blank=True, null=True)
    execution_date = models.DateField(_("Execution date"),
                                      blank=True,
                                      null=True)

    # show_items = dd.ShowSlaveTable('finan.ItemsByPaymentOrder')
    write_xml = WritePaymentsInitiation()

    @dd.displayfield(_("Print"))
    def print_actions(self, ar):
        if ar is None:
            return ''
        elems = []
        elems.append(ar.instance_action_button(self.write_xml))
        return E.p(*join_elems(elems, sep=", "))

    def get_wanted_movements(self):
        """Implements
        :meth:`lino_xl.lib.ledger.Voucher.get_wanted_movements`
        for payment orders.

        As a side effect this also computes the :attr:`total` field and saves
        the voucher.

        """
        # dd.logger.info("20151211 cosi.PaymentOrder.get_wanted_movements()")
        acc = self.journal.account
        if not acc:
            warn_jnl_account(self.journal)
        # TODO: what if the needs_partner of the journal's account
        # is not checked? Shouldn't we raise an error here?
        amount, movements_and_items = self.get_finan_movements()
        if abs(amount) > MAX_AMOUNT:
            # dd.logger.warning("Oops, {} is too big ({})".format(amount, self))
            raise Exception("Oops, {} is too big ({})".format(amount, self))
            return
        self.total = self.journal.dc.normalized_amount(
            -amount)  # PaymentOrder.get_wanted_movements()
        # if self.journal.dc == DC.debit:  # PaymentOrder.get_wanted_movements()
        #     self.total = -amount
        # else:
        #     self.total = amount
        item_partner = self.journal.partner is None
        for m, i in movements_and_items:
            yield m
            if item_partner:
                yield self.create_movement(
                    i,
                    (acc, None),
                    m.project,
                    -m.amount,  # 20201219 PaymentOrder.get_wanted_movements
                    partner=m.partner,
                    match=i.get_match())
        if not item_partner:
            yield self.create_movement(
                None,
                (acc, None),
                None,
                -amount,  # 20201219 PaymentOrder.get_wanted_movements
                partner=self.journal.partner,
                match=self)
            # 20191226 partner=self.journal.partner, match=self.get_default_match())
        # no need to save() because this is called during set_workflow_state()
        # self.full_clean()
        # self.save()

    def add_item_from_due(self, obj, **kwargs):
        # if obj.bank_account is None:
        #     return
        i = super(PaymentOrder, self).add_item_from_due(obj, **kwargs)
        i.bank_account = obj.bank_account
        return i
Ejemplo n.º 21
0
class FinancialVoucherItem(VoucherItem, SequencedVoucherItem, ProjectRelated,
                           Matching):
    class Meta:
        abstract = True
        verbose_name = _("Item")
        verbose_name_plural = _("Items")
        app_label = "finan"

    amount = dd.PriceField(_("Amount"), default=ZERO, null=False)
    # dc = DebitOrCreditField()
    remark = models.CharField(_("Your reference"), max_length=200, blank=True)
    account = dd.ForeignKey('ledger.Account', blank=True, null=True)
    partner = dd.ForeignKey('contacts.Partner', blank=True, null=True)

    quick_search_fields = 'remark account__ref account__name partner__name'

    @dd.chooser(simple_values=True)
    def match_choices(cls, voucher, partner):
        fkw = {}
        if not dd.plugins.finan.suggest_future_vouchers:
            fkw.update(value_date__lte=voucher.entry_date)
        return cls.get_match_choices(voucher.journal, partner, **fkw)

    def add_item_from_due(self, obj, **kwargs):
        return self.voucher.add_item_from_due(obj, **kwargs)

    # def get_default_match(self): # 20191226
    def __str__(self):
        """Used as `match` when no explicit match is specified for
        this movement.

        """
        if self.voucher_id and self.voucher.journal_id:
            return "%s %s:%s" % (self.voucher.journal.ref, self.voucher.number,
                                 self.seqno)
            # return str(self.date)
        return models.Model.__str__(self)
        # return super(FinancialVoucherItem, self).__str__()

    def get_siblings(self):
        return self.voucher.items.all()

    def collect_suggestions(self, ar, flt):
        suggestions = list(
            ledger.get_due_movements(self.voucher.journal.dc, flt))
        # dd.logger.info("20210106 %s", suggestions)
        if len(suggestions) == 0:
            self.match = ""
        elif len(suggestions) == 1:
            self.fill_suggestion(suggestions[0])
        elif ar:
            self.match = _("{} suggestions").format(len(suggestions))

            # def ok(ar2):
            #     # self.fill_suggestion(suggestions[0])
            #     # self.set_grouper(suggestions)
            #     ar2.error(_("Oops, not implemented."))
            #     return

            # elems = ["Cool! ", E.b(str(self.partner)),
            #          " has ", E.b(str(len(suggestions))),
            #          " suggestions! Click "]
            # ba = ar.actor.get_action_by_name('suggest')
            # elems.append(ar.action_button(ba, self))
            # elems.append(".")
            # html = E.p(*elems)
            # # dd.logger.info("20160526 %s", E.tostring(html))
            # ar.success(E.tostring(html), alert=True)
            # # ar.confirm(ok, E.tostring(html))

    def match_changed(self, ar):
        if not self.match or not self.voucher.journal.auto_fill_suggestions:
            return
        flt = dict(match=self.match, cleared=False)
        if not dd.plugins.finan.suggest_future_vouchers:
            flt.update(value_date__lte=self.voucher.entry_date)
        self.collect_suggestions(ar, models.Q(**flt))
        self.guess_amount()

    def partner_changed(self, ar):
        # dd.logger.info("20210106 FinancialMixin.partner_changed %s", self.amount)
        if self.amount:
            return
        if not self.partner or not self.voucher.journal.auto_fill_suggestions:
            return
        flt = dict(partner=self.partner, cleared=False)
        if not dd.plugins.finan.suggest_future_vouchers:
            flt.update(value_date__lte=self.voucher.entry_date)
        if self.match:
            flt.update(match=self.match)
        self.collect_suggestions(ar, models.Q(**flt))
        self.guess_amount()

    def debit_changed(self, ar):
        self.guess_amount()

    def credit_changed(self, ar):
        self.guess_amount()

    def amount_changed(self, ar):
        self.guess_amount()

    def account_changed(self, ar):
        if self.amount:
            return
        if not self.account:
            return
        if self.account.default_amount:
            self.amount = self.voucher.journal.dc.normalized_amount(
                self.account.default_amount)
            return
        if self.account.clearable and self.voucher.journal.auto_fill_suggestions:
            flt = dict(account=self.account,
                       partner=self.partner,
                       cleared=False)
            if not dd.plugins.finan.suggest_future_vouchers:
                flt.update(value_date__lte=self.voucher.entry_date)
            if self.match:
                flt.update(match=self.match)
            self.collect_suggestions(ar, models.Q(**flt))
        self.guess_amount()

    def get_partner(self):
        return self.partner or self.project

    def fill_suggestion(self, due):
        """Fill the fields of this item from the given suggestion (a
        `DueMovement` instance).

        """
        for k, v in self.voucher.due2itemdict(due).items():
            setattr(self, k, v)

    def guess_amount(self):
        if self.amount:
            return
        self.amount = ZERO
        if self.voucher.auto_compute_amount:
            q = self.voucher.items.exclude(id=self.id).annotate(
                models.Sum('amount'))
            # print("20210108", q[0].amount__sum)
            self.amount = -myround(q[0].amount__sum)

    def full_clean(self, *args, **kwargs):
        if self.amount is None:
            self.amount = ZERO  # just in case...

        if True:  # temporarily deactivated for data migration
            problems = list(FinancialVoucherItemChecker.check_instance(self))
            if len(problems):
                raise ValidationError("20181120 {}".format('\n'.join(
                    [str(p[1]) for p in problems])))

        # dd.logger.info("20151117 FinancialVoucherItem.full_clean a %s", self.amount)
        super(FinancialVoucherItem, self).full_clean(*args, **kwargs)
Ejemplo n.º 22
0
 def get_model_field(self):
     return dd.PriceField(
         self.text, default=Decimal, editable=self.editable,
         help_text=self.help_text)
Ejemplo n.º 23
0
class Movement(ProjectRelated):
    """Represents an accounting movement in the ledger.

    .. attribute:: value_date

        The date at which this movement is to be entered into the
        ledger.  This is usually the voucher's :attr:`entry_date
        <lino_cosi.lib.ledger.models.Voucher.entry_date>`, except
        e.g. for bank statements where each item can have its own
        value date.

    .. attribute:: voucher

        Pointer to the :class:`Voucher` who caused this movement.

    .. attribute:: partner

        Pointer to the partner involved in this movement. This may be
        blank.

    .. attribute:: seqno

        Sequential number within a voucher.

    .. attribute:: account

        Pointer to the :class:`Account` that is being moved by this movement.

    .. attribute:: amount
    .. attribute:: dc

    .. attribute:: match

        Pointer to the :class:`Movement` that is being cleared by this
        movement.

    .. attribute:: cleared

        Whether

    .. attribute:: voucher_partner

        A virtual field which returns the *partner of the voucher*.
        For incoming invoices this is the supplier, for outgoing
        invoices this is the customer, for financial vouchers this is
        empty.

    .. attribute:: voucher_link

        A virtual field which shows a link to the voucher.

    .. attribute:: match_link

        A virtual field which shows a clickable variant of the match
        string. Clicking it will open a table with all movements
        having that match.

    """
    allow_cascaded_delete = ['voucher']

    class Meta:
        app_label = 'ledger'
        verbose_name = _("Movement")
        verbose_name_plural = _("Movements")

    voucher = models.ForeignKey(Voucher)

    partner = dd.ForeignKey(
        'contacts.Partner',
        related_name="%(app_label)s_%(class)s_set_by_partner",
        blank=True,
        null=True)

    seqno = models.IntegerField(_("Seq.No."))

    account = dd.ForeignKey('accounts.Account')
    amount = dd.PriceField(default=0)
    dc = DebitOrCreditField()

    match = models.CharField(_("Match"), blank=True, max_length=20)

    # match = MatchField(blank=True, null=True)

    cleared = models.BooleanField(_("Cleared"), default=False)
    # 20160327: rename "satisfied" to "cleared"

    value_date = models.DateField(_("Value date"), null=True, blank=True)

    @dd.chooser(simple_values=True)
    def match_choices(cls, partner, account):
        qs = cls.objects.filter(partner=partner,
                                account=account,
                                cleared=False)
        qs = qs.order_by('value_date')
        return qs.values_list('match', flat=True)

    def select_text(self):
        v = self.voucher.get_mti_leaf()
        return "%s (%s)" % (v, v.entry_date)

    @dd.virtualfield(dd.PriceField(_("Debit")))
    def debit(self, ar):
        if self.dc:
            return None
        return self.amount

    @dd.virtualfield(dd.PriceField(_("Credit")))
    def credit(self, ar):
        if self.dc:
            return self.amount
        return None

    @dd.displayfield(_("Voucher"))
    def voucher_link(self, ar):
        if ar is None:
            return ''
        return ar.obj2html(self.voucher.get_mti_leaf())

    @dd.displayfield(_("Voucher partner"))
    def voucher_partner(self, ar):
        if ar is None:
            return ''
        voucher = self.voucher.get_mti_leaf()
        p = voucher.get_partner()
        if p is None:
            return ''
        return ar.obj2html(p)

    @dd.displayfield(_("Match"))
    def match_link(self, ar):
        if ar is None or not self.match:
            return ''
        sar = rt.modules.ledger.MovementsByMatch.request(
            master_instance=self.match, parent=ar)
        return sar.ar2button(label=self.match)

    #~ @dd.displayfield(_("Matched by"))
    #~ def matched_by(self,ar):
    #~ elems = [obj.voucher_link(ar) for obj in Movement.objects.filter(match=self)]
    #~ return E.div(*elems)

    def get_siblings(self):
        return self.voucher.movement_set.order_by('seqno')
        #~ return self.__class__.objects.filter().order_by('seqno')

    def __str__(self):
        return "%s.%d" % (unicode(self.voucher), self.seqno)

    # def get_match(self):
    #     return self.match or str(self.voucher)

    @classmethod
    def get_balance(cls, dc, qs):
        bal = ZERO
        for mvt in qs:
            if mvt.dc == dc:
                bal += mvt.amount
            else:
                bal -= mvt.amount
        return bal

    @classmethod
    def balance_info(cls, dc, **kwargs):
        qs = cls.objects.filter(**kwargs)
        qs = qs.order_by('value_date')
        bal = ZERO
        s = ''
        for mvt in qs:
            amount = mvt.amount
            if mvt.dc == dc:
                bal -= amount
                s += ' -' + str(amount)
            else:
                bal += amount
                s += ' +' + str(amount)
            s += " ({0}) ".format(mvt.voucher)
            # s += " ({0} {1}) ".format(
            #     mvt.voucher,
            #     dd.fds(mvt.voucher.voucher_date))
        if bal:
            return s + "= " + str(bal)
        return ''
        if False:
            mvts = []
            for dm in get_due_movements(CREDIT, partner=self.pupil):
                s = dm.match
                s += " [{0}]".format(day_and_month(dm.due_date))
                s += " ("
                s += ', '.join([str(i.voucher) for i in dm.debts])
                if len(dm.payments):
                    s += " - "
                    s += ', '.join([str(i.voucher) for i in dm.payments])
                s += "): {0}".format(dm.balance)
                mvts.append(s)
            return '\n'.join(mvts)
Ejemplo n.º 24
0
class Account(mixins.BabelNamed, mixins.Sequenced, mixins.Referrable):
    """An **account** is an item of an account chart used to collect
    ledger transactions or other accountable items.

    .. attribute:: name

        The multilingual designation of this account, as the users see
        it.


    .. attribute:: group

        The *account group* to which this account belongs.  Points to
        an instance of :class:`Group`.  If this field is empty, the
        account won't appear in certain reports.
    
    .. attribute:: seqno

        The sequence number of this account within its :attr:`group`.
    
    .. attribute:: ref

        An optional unique name which can be used to reference a given
        account.

    .. attribute:: type

        The *account type* of this account.  This points to an item of
        :class:`AccountTypes
        <lino_cosi.lib.accounts.choicelists.AccountTypes>`.
    
    .. attribute:: needs_partner

        Whether bookings to this account need a partner specified.

        This causes the contra entry of financial documents to be
        detailed (i.e. one for every item) or not (i.e. a single
        contra entry per voucher, without project nor partner).

    .. attribute:: default_amount

        The default amount to book in bank statements or journal
        entries when this account has been selected manually. The
        default booking direction is that of the :attr:`type`.

    """
    ref_max_length = settings.SITE.plugins.accounts.ref_length

    class Meta:
        verbose_name = _("Account")
        verbose_name_plural = _("Accounts")
        ordering = ['ref']

    group = models.ForeignKey('accounts.Group', blank=True, null=True)
    type = AccountTypes.field()  # blank=True)
    needs_partner = models.BooleanField(_("Needs partner"), default=False)
    clearable = models.BooleanField(_("Clearable"), default=False)
    # default_dc = DebitOrCreditField(_("Default booking direction"))
    default_amount = dd.PriceField(_("Default amount"), blank=True, null=True)

    def full_clean(self, *args, **kw):
        if self.group_id is not None:
            if not self.ref:
                qs = rt.modules.accounts.Account.objects.all()
                self.ref = str(qs.count() + 1)
            if not self.name:
                self.name = self.group.name
            self.type = self.group.account_type

        # if self.default_dc is None:
        #     self.default_dc = self.type.dc
        super(Account, self).full_clean(*args, **kw)

    def __str__(self):
        return "(%(ref)s) %(title)s" % dict(
            ref=self.ref, title=settings.SITE.babelattr(self, 'name'))
Ejemplo n.º 25
0
class DebtorsCreditors(dd.VirtualTable):
    required_roles = dd.login_required(AccountingReader)
    auto_fit_column_widths = True
    column_names = "age due_date partner partner_id balance vouchers"
    display_mode = 'html'
    abstract = True

    parameters = mixins.Today()
    # params_layout = "today"

    d_or_c = NotImplementedError

    @classmethod
    def get_data_rows(self, ar):
        rows = []
        mi = ar.master_instance
        if mi is None:  # called directly from main menu
            if ar.param_values is None:
                return rows
            end_date = ar.param_values.today
        else:  # called from Situation report
            end_date = mi.today
        get_due_movements = rt.models.ledger.get_due_movements
        qs = rt.models.contacts.Partner.objects.order_by('name')
        for row in qs:
            row._balance = ZERO
            row._due_date = None
            row._expected = tuple(
                get_due_movements(
                    self.d_or_c, models.Q(partner=row,
                                          value_date__lte=end_date)))
            for dm in row._expected:
                row._balance += dm.balance
                if dm.due_date is not None:
                    if row._due_date is None or row._due_date > dm.due_date:
                        row._due_date = dm.due_date
                # logger.info("20140105 %s %s", row, dm)

            if row._balance > ZERO:
                rows.append(row)

        def k(a):
            return a._due_date

        rows.sort(key=k)
        return rows

    # @dd.displayfield(_("Partner"))
    # def partner(self, row, ar):
    #     return ar.obj2html(row)

    @dd.virtualfield(dd.ForeignKey('contacts.Partner'))
    def partner(self, row, ar):
        return row

    @dd.virtualfield(models.IntegerField(_("ID")))
    def partner_id(self, row, ar):
        return row.pk

    @dd.virtualfield(dd.PriceField(_("Balance")))
    def balance(self, row, ar):
        return row._balance

    @dd.virtualfield(models.DateField(_("Due date")))
    def due_date(self, row, ar):
        return row._due_date

    @dd.virtualfield(models.IntegerField(_("Age")))
    def age(self, row, ar):
        dd = ar.param_values.today - row._due_date
        return dd.days

    @dd.displayfield(_("Vouchers"))
    def vouchers(self, row, ar):
        matches = [dm.match for dm in row._expected]
        return E.span(', '.join(matches))
Ejemplo n.º 26
0
class ItemEntry(ReportEntry):
    class Meta:
        app_label = 'sheets'
        abstract = dd.is_abstract_model(__name__, 'ItemEntry')
        verbose_name = _("Sheet item entry")
        verbose_name_plural = _("Sheet item entries")

    # allow_cascaded_delete = ['item']

    item = dd.ForeignKey('sheets.Item')

    @dd.displayfield(_("Description"))
    def description(self, ar=None):
        # print(20180831, ar.renderer, ar.user)
        return self.item.description

    @classmethod
    def get_collectors(cls):
        yield Collector(cls, 'item', 'account__sheet_item')

    @classmethod
    def setup_parameters(cls, fields):
        fields.setdefault(
            'sheet_type', SheetTypes.field())
        super(ItemEntry, cls).setup_parameters(fields)

    @classmethod
    def get_request_queryset(self, ar, **filter):
        qs = super(ItemEntry, self).get_request_queryset(ar, **filter)
        pv = ar.param_values
        if pv.sheet_type:
            qs = qs.filter(item__sheet_type=pv.sheet_type)
        return qs

    @classmethod
    def get_title_tags(self, ar):
        for t in super(ItemEntry, self).get_title_tags(ar):
            yield t
        pv = ar.param_values
        if pv.sheet_type:
            yield str(pv.sheet_type)

    @dd.virtualfield(dd.PriceField(_("Activa")))
    def activa(self, ar):
        if self.item.dc is CREDIT:
            return self.new_balance().c

    @dd.virtualfield(dd.PriceField(_("Passiva")))
    def passiva(self, ar):
        if self.item.dc is DEBIT:
            return self.new_balance().d

    # @dd.displayfield(_("Activa"), max_length=12)
    # def activa(self, ar):
    #     if self.item.dc:
    #         return self.value2html(ar)

    # @dd.displayfield(_("Passiva"), max_length=12)
    # def passiva(self, ar):
    #     if not self.item.dc:
    #         return self.value2html(ar)

    @dd.virtualfield(dd.PriceField(_("Expenses")))
    def expenses(self, ar):
        if self.item.dc is DEBIT:
            return self.new_balance().d

    @dd.virtualfield(dd.PriceField(_("Revenues")))
    def revenues(self, ar):
        if self.item.dc is CREDIT:
            return self.new_balance().c
Ejemplo n.º 27
0
class AccountBalances(dd.Table):
    editable = False
    required_roles = dd.login_required(AccountingReader)
    auto_fit_column_widths = True
    column_names = "description old_d old_c empty_column:1 during_d during_c empty_column:1 new_d new_c"
    display_mode = 'html'
    abstract = True
    params_panel_hidden = False
    use_as_default_table = False

    parameters = AccountingPeriodRange()
    params_layout = "start_period end_period"

    @classmethod
    def rowmvtfilter(self):
        raise NotImplementedError()

    @classmethod
    def get_request_queryset(self, ar, **kwargs):

        # see https://docs.djangoproject.com/en/1.11/ref/models/expressions/#using-aggregates-within-a-subquery-expression
        AccountingPeriod = rt.models.ledger.AccountingPeriod
        pv = ar.param_values
        sp = pv.start_period or AccountingPeriod.get_default_for_date(
            dd.today())
        ep = pv.end_period or sp

        qs = super(AccountBalances, self).get_request_queryset(ar)

        flt = self.rowmvtfilter(ar)
        oldflt = dict()
        oldflt.update(flt)
        duringflt = dict()
        duringflt.update(flt)
        during_periods = AccountingPeriod.objects.filter(ref__gte=sp.ref,
                                                         ref__lte=ep.ref)
        before_periods = AccountingPeriod.objects.filter(ref__lt=sp.ref)
        oldflt.update(voucher__accounting_period__in=before_periods)
        duringflt.update(voucher__accounting_period__in=during_periods)

        outer_link = self.model._meta.model_name

        def addann(kw, name, dc, flt):
            mvts = rt.models.ledger.Movement.objects.filter(dc=dc, **flt)
            mvts = mvts.order_by()
            mvts = mvts.values(outer_link)  # this was the important thing
            mvts = mvts.annotate(
                total=Sum('amount', output_field=dd.PriceField()))
            mvts = mvts.values('total')
            kw[name] = Subquery(mvts, output_field=dd.PriceField())

        kw = dict()
        addann(kw, 'old_d', DEBIT, oldflt)
        addann(kw, 'old_c', CREDIT, oldflt)
        addann(kw, 'during_d', DEBIT, duringflt)
        addann(kw, 'during_c', CREDIT, duringflt)

        qs = qs.annotate(**kw)

        qs = qs.exclude(old_d=ZERO, old_c=ZERO, during_d=ZERO, during_c=ZERO)

        # print("20170930 {}".format(qs.query))
        return qs

    @classmethod
    def new_balance(cls, row):
        return Balance(row.old_d, row.old_c) + Balance(row.during_d,
                                                       row.during_c)

    @classmethod
    def normal_dc(cls, row, ar):
        # raise NotImplementedError()
        return DEBIT  # row.normal_dc

    @dd.displayfield(_("Reference"))
    def ref(self, row, ar):
        return row.ref

    @dd.displayfield(_("Description"))
    def description(self, row, ar):
        # print(20180831, ar.renderer, ar.user)
        return row.obj2href(ar)

    @dd.virtualfield(dd.PriceField(_("Old balance")))
    def old_dc(self, row, ar):
        return Balance(row.old_d, row.old_c).value(self.normal_dc(row, ar))

    @dd.virtualfield(dd.PriceField(_("Movements")))
    def during_dc(self, row, ar):
        return Balance(row.during_d,
                       row.during_c).value(self.normal_dc(row, ar))

    @dd.virtualfield(dd.PriceField(_("New balance")))
    def new_dc(self, row, ar):
        return self.new_balance(row).value(self.normal_dc(row, ar))

    @dd.virtualfield(dd.PriceField(_("Debit before")))
    def old_d(self, row, ar):
        return Balance(row.old_d, row.old_c).d

    @dd.virtualfield(dd.PriceField(_("Credit before")))
    def old_c(self, row, ar):
        return Balance(row.old_d, row.old_c).c

    @dd.virtualfield(dd.PriceField(_("Debit")))
    def during_d(self, row, ar):
        return row.during_d

    @dd.virtualfield(dd.PriceField(_("Credit")))
    def during_c(self, row, ar):
        return row.during_c

    @dd.virtualfield(dd.PriceField(_("Debit after")))
    def new_d(self, row, ar):
        return self.new_balance(row).d

    @dd.virtualfield(dd.PriceField(_("Credit after")))
    def new_c(self, row, ar):
        return self.new_balance(row).c

    @dd.displayfield("", max_length=0)
    def empty_column(self, row, ar):
        return ''
Ejemplo n.º 28
0
class VatTotal(dd.Model):
    """Model mixin which defines the fields `total_incl`, `total_base`
    and `total_vat`.  Used for both the document header
    (:class:`VatDocument`) and for each item (:class:`VatItemBase`).

    .. attribute:: total_incl
    
        A :class:`lino.core.fields.PriceField` which stores the total
        amount VAT *included*.

    .. attribute:: total_base

        A :class:`lino.core.fields.PriceField` which stores the total
        amount VAT *excluded*.

    .. attribute:: total_vat

        A :class:`lino.core.fields.PriceField` which stores the amount
        of VAT.

    """
    class Meta:
        abstract = True

    # price = dd.PriceField(_("Total"),blank=True,null=True)
    total_incl = dd.PriceField(_("Total incl. VAT"), blank=True, null=True)
    total_base = dd.PriceField(_("Total excl. VAT"), blank=True, null=True)
    total_vat = dd.PriceField(_("VAT"), blank=True, null=True)

    _total_fields = set('total_vat total_base total_incl'.split())
    # For internal use.  This is the list of field names to disable
    # when `auto_compute_totals` is True.

    auto_compute_totals = False
    """Set this to `True` on subclasses who compute their totals
    automatically, i.e. the fields :attr:`total_base`,
    :attr:`total_vat` and :attr:`total_incl` are disabled.  This is
    set to `True` for :class:`lino_cosi.lib.sales.models.SalesDocument`.

    """

    def disabled_fields(self, ar):
        """Disable all three total fields if `auto_compute_totals` is set,
        otherwise disable :attr:`total_vat` if
        :attr:`VatRule.can_edit` is False.

        """
        fields = super(VatTotal, self).disabled_fields(ar)
        if self.auto_compute_totals:
            fields |= self._total_fields
        else:
            rule = self.get_vat_rule()
            if rule is not None and not rule.can_edit:
                fields.add('total_vat')
        return fields

    def reset_totals(self, ar):
        pass

    def get_vat_rule(self):
        """Return the VAT rule for this voucher or voucher item. Called when
        user edits a total field in the document header when
        `auto_compute_totals` is False.

        """
        return None

    def total_base_changed(self, ar):
        """Called when user has edited the `total_base` field.  If total_base
        has been set to blank, then Lino fills it using
        :meth:`reset_totals`. If user has entered a value, compute
        :attr:`total_vat` and :attr:`total_incl` from this value using
        the vat rate. If there is no VatRule, `total_incl` and
        `total_vat` are set to None.

        If there are rounding differences, `total_vat` will get them.

        """
        # logger.info("20150128 total_base_changed %r", self.total_base)
        if self.total_base is None:
            self.reset_totals(ar)
            if self.total_base is None:
                return

        rule = self.get_vat_rule()
        # logger.info("20150128 %r", rule)
        if rule is None:
            self.total_incl = None
            self.total_vat = None
        else:
            self.total_incl = myround(self.total_base * (ONE + rule.rate))
            self.total_vat = self.total_incl - self.total_base

    def total_vat_changed(self, ar):
        """Called when user has edited the `total_vat` field.  If it has been
        set to blank, then Lino fills it using
        :meth:`reset_totals`. If user has entered a value, compute
        :attr:`total_incl`. If there is no VatRule, `total_incl` is
        set to None.

        """
        if self.total_vat is None:
            self.reset_totals(ar)
            if self.total_vat is None:
                return

        if self.total_base is None:
            self.total_base = ZERO
        self.total_incl = self.total_vat + self.total_base

    def total_incl_changed(self, ar):
        """Called when user has edited the `total_incl` field.  If total_incl
        has been set to blank, then Lino fills it using
        :meth:`reset_totals`. If user enters a value, compute
        :attr:`total_base` and :attr:`total_vat` from this value using
        the vat rate. If there is no VatRule, `total_incl` should be
        disabled, so this method will never be called.

        If there are rounding differences, `total_vat` will get them.

        """
        if self.total_incl is None:
            self.reset_totals(ar)
            if self.total_incl is None:
                return
        # assert not isinstance(self.total_incl,basestring)
        rule = self.get_vat_rule()
        if rule is None:
            self.total_base = None
            self.total_vat = None
        else:
            self.total_base = myround(self.total_incl / (ONE + rule.rate))
            self.total_vat = myround(self.total_incl - self.total_base)
Ejemplo n.º 29
0
class AccountInvoice(BankAccount, Payable, RegistrableVoucher, Matching,
                     ProjectRelated):
    class Meta:
        app_label = 'vatless'
        verbose_name = _("Invoice")
        verbose_name_plural = _("Invoices")

    state = VoucherStates.field(default='draft')
    amount = dd.PriceField(_("Amount"), blank=True, null=True)

    # _total_fields = set(['amount'])
    # """The list of field names to disable when `edit_totals` is
    # False.
    #
    # """
    #
    # edit_totals = False
    #
    # def disabled_fields(self, ar):
    #     """Disable all three total fields if `edit_totals` is False,
    #     otherwise disable :attr:`total_vat` if
    #     :attr:`VatRule.can_edit` is False.
    #
    #     """
    #     fields = super(AccountInvoice, self).disabled_fields(ar)
    #     if not self.edit_totals:
    #         fields |= self._total_fields
    #     return fields

    def get_partner(self):
        return self.partner or self.project

    def compute_totals(self):
        if self.pk is None:
            return
        base = Decimal()
        for i in self.items.all():
            if i.amount is not None:
                base += i.amount
        self.amount = base

    def get_payable_sums_dict(self):
        tt = self.get_trade_type()
        sums = SumCollector()
        for i in self.items.order_by('seqno'):
            if i.amount:
                b = i.get_base_account(tt)
                if b is None:
                    raise Exception("No base account for %s (amount is %r)" %
                                    (i, i.amount))
                sums.collect(((b, i.get_ana_account()), i.project
                              or self.project, None, None), i.amount)
        return sums

    def full_clean(self, *args, **kw):
        self.compute_totals()
        super(AccountInvoice, self).full_clean(*args, **kw)

    def before_state_change(self, ar, old, new):
        if new.name == 'registered':
            self.compute_totals()
        elif new.name == 'draft':
            pass
        super(AccountInvoice, self).before_state_change(ar, old, new)
Ejemplo n.º 30
0
class ExpectedMovements(dd.VirtualTable):
    row_height = 4
    required_roles = dd.login_required(AccountingReader)
    label = _("Debts")
    icon_name = 'book_link'
    #~ column_names = 'match due_date debts payments balance'
    column_names = 'due_date:15 balance debts payments'
    auto_fit_column_widths = True
    # variable_row_height = True
    parameters = dd.ParameterPanel(
        date_until=models.DateField(_("Date until"), blank=True, null=True),
        trade_type=TradeTypes.field(blank=True),
        from_journal=dd.ForeignKey('ledger.Journal', blank=True),
        for_journal=dd.ForeignKey('ledger.Journal',
                                  blank=True,
                                  verbose_name=_("Clearable by")),
        account=dd.ForeignKey('ledger.Account', blank=True),
        partner=dd.ForeignKey('contacts.Partner', blank=True),
        project=dd.ForeignKey(dd.plugins.ledger.project_model, blank=True),
        show_sepa=dd.YesNo.field(_("With SEPA"), blank=True),
        same_dc=dd.YesNo.field(_("Same D/C"), blank=True),
    )
    params_layout = """
    trade_type date_until from_journal for_journal
    project partner account show_sepa same_dc"""

    @classmethod
    def get_dc(cls, ar=None):
        return CREDIT

    @classmethod
    def get_data_rows(cls, ar, **flt):
        #~ if ar.param_values.journal:
        #~ pass
        pv = ar.param_values
        # if pv is None:
        #     raise Exception("No pv in %s" % ar)
        if pv.trade_type:
            flt.update(account=pv.trade_type.get_main_account())
        if pv.partner:
            flt.update(partner=pv.partner)
        if pv.account:
            flt.update(account=pv.account)
        if pv.project:
            flt.update(project=pv.project)

        if pv.show_sepa == dd.YesNo.yes:
            flt.update(partner__sepa_accounts__primary=True)
        elif pv.show_sepa == dd.YesNo.no:
            flt.update(partner__sepa_accounts__primary__isnull=True)

        if pv.same_dc == dd.YesNo.yes:
            flt.update(dc=cls.get_dc(ar))
        elif pv.same_dc == dd.YesNo.no:
            flt.update(dc=not cls.get_dc(ar))

        if pv.date_until is not None:
            flt.update(value_date__lte=pv.date_until)
        if pv.for_journal is not None:
            accounts = rt.models.ledger.Account.objects.filter(
                matchrule__journal=pv.for_journal).distinct()
            flt.update(account__in=accounts)
        if pv.from_journal is not None:
            flt.update(voucher__journal=pv.from_journal)
        flt = models.Q(**flt)
        if ar.quick_search:
            flt &= rt.models.contacts.Partner.quick_search_filter(
                ar.quick_search, prefix='partner__')

        return rt.models.ledger.get_due_movements(cls.get_dc(ar), flt)

    @classmethod
    def get_pk_field(self):
        return rt.models.ledger.Movement._meta.pk

    @classmethod
    def get_row_by_pk(cls, ar, pk):
        # this is tricky.
        # for i in ar.data_iterator:
        #     if i.id == pk:
        #         return i
        # raise Exception("Not found: %s in %s" % (pk, ar))
        mvt = rt.models.ledger.Movement.objects.get(pk=pk)
        dm = rt.models.ledger.DueMovement(cls.get_dc(ar), mvt)
        dm.collect_all()
        return dm

    @dd.displayfield(_("Info"))
    def info(self, row, ar):
        elems = []
        if row.project:
            elems.append(ar.obj2html(row.project))
        if row.partner:
            elems.append(ar.obj2html(row.partner))
            # elems.append(row.partner.address)
        if row.bank_account:
            elems.append(ar.obj2html(row.bank_account))
        if row.account:
            elems.append(ar.obj2html(row.account))
        # return E.span(*join_elems(elems, ' / '))
        return E.span(*join_elems(elems, E.br))
        # return E.span(*elems)

    @dd.displayfield(_("Match"))
    def match(self, row, ar):
        return row.match

    @dd.virtualfield(
        models.DateField(
            _("Due date"),
            help_text=_("Due date of the eldest debt in this match group")))
    def due_date(self, row, ar):
        return row.due_date

    @dd.displayfield(_("Debts"),
                     help_text=_("List of invoices in this match group"))
    def debts(self, row, ar):
        return E.span(*join_elems([  # E.p(...) until 20150128
            ar.obj2html(i.voucher.get_mti_leaf()) for i in row.debts
        ]))

    @dd.displayfield(_("Payments"),
                     help_text=_("List of payments in this match group"))
    def payments(self, row, ar):
        return E.span(*join_elems([  # E.p(...) until 20150128
            ar.obj2html(i.voucher.get_mti_leaf()) for i in row.payments
        ]))

    @dd.virtualfield(dd.PriceField(_("Balance")))
    def balance(self, row, ar):
        return row.balance

    @dd.virtualfield(dd.ForeignKey('contacts.Partner'))
    def partner(self, row, ar):
        return row.partner

    @dd.virtualfield(dd.ForeignKey(dd.plugins.ledger.project_model))
    def project(self, row, ar):
        return row.project

    @dd.virtualfield(dd.ForeignKey('ledger.Account'))
    def account(self, row, ar):
        return row.account

    @dd.virtualfield(
        dd.ForeignKey('sepa.Account', verbose_name=_("Bank account")))
    def bank_account(self, row, ar):
        return row.bank_account