.. _reference-commerce-syntax:

==============
Summary syntax
==============

A summary describes the components of a financial statement (such as an order, shopping cart, or invoice). The data behind these components is stored in your models and can take any form you wish. When you define a Summary class, you describe how your data model forms a financial statement.

To do this, all elements of a financial statement are divided into one of three roles:

- A list of items
- A single extra cost or deduction
- A total of one or more items and extra costs

For example, the following is a very simple Summary class::

   from rollyourown import commerce

   class CartSummary(commerce.Summary):
       products = commerce.Items()
       delivery = commerce.Extra()
       total    = commerce.Total()

In this typical example, the financial statement (a shopping cart) contains a list of products, an additional cost for delivery and a grand total. All possible elements of financial statements should be able to be put in one of these three roles. Here is a small collection of real world examples.

Items
  list of products, work sessions, expenses, taxes, deductions/adjustments, discounts, gift vouchers, payments already made, movies watched in hotel room, etc

Extra
  single discount, single gift voucher, single tax, shipping/delivery cost, commission, fees/surcharges, etc

Total
  grand total, pretax total, total of all taxes, etc

When this is defined, the framework treats ``Items`` and ``Extra`` as input and provides ``Total`` as output. Side benefits of this process include a very clear organisation of the calculation of these totals. If you need to change how this process is done, it should be very clear what changes are required.

Each summary is eventually linked to a Django model instance. The fields of the model provide the data for the ``Items`` and ``Extra`` input, and these can be specified as explained below.


Items
=====

When you add a list of items to your summary class, you need to specify which field or attribute from the relevant model provides the required data. This field is generally a ``ManyToManyField``, but could also be a reverse ``ForeignKey`` field, if that's how you've defined your model.

.. class:: rollyourown.commerce.Items(attribute, item_amount_from, cache_amount_as)

All arguments are optional.

Arguments
~~~~~~~~~

.. attribute:: Items.attribute

    Which attribute, field or method provides the list of items. By default it is the same as name you give to the Summary class attribute.

.. attribute:: Items.item_amount_from

    Which attribute, field or method on each item provides the amount to be used in calculating totals.

    - If you are referencing the Summary instance, use ``"self.XYZ"``, which is called with a single argument (the model instance). 
    - If you are referencing the model instance, use ``"model.XYZ"``. If this is a method, it is called with no arguments.
    - You can also pass a callable, which is called with the model instance as an argument.

    The default value is ``"self.get_X_amount"``, where X is the name of the summary class attribute.

.. attribute:: Items.editable

    When the summary is displayed as a formset (see :ref:`Summary Formsets <reference-summary-formsets>`), these fields will be editable.
    
    - If ``editable`` is set to ``True``, a standard model formset is used with ``delete=True``.  If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
    - If ``editable`` is set to a string, a standard form field is used for the relevant attribute (using the items model as a reference). A ``delete`` form field is also provided. If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
    - If ``editable`` is set to a form, then this form is used. The custom form must accept a keyword argument (``instance``) and have a ``.save()`` method if the data is to be saved.

    To Customise the way forms are saved, you can overload the ``.save_form(formset)`` method on the Summary class.

.. attribute:: Items.cache_amount_as

    When a total involving these items is calculated, the amount of each item is stored as an attribute on the relevant model instance. The given string will be the name of this new attribute.  The default value is ``"AMOUNT"``.

    - This argument cannot be ``None``. Note that, you don't need to create a field for this, attributes can be added to django models at run time and should not affect the operation of the model. If you do create a field for this value, note that it will not be saved automatically. If you want to store the value, you might like to do so when each item is saved using Django's usual mechanisms (``pre_save`` signal or overloading the ``save()`` method).

.. note::
    One advantage of this framework is that it avoids recalculating things as much as possible. To allow this it makes the assumption that the database does not change once the summary has been created. If you update one of your items or extras after creating the summary, the changes may not appear.


Example
~~~~~~~

::

    class MySummary(commerce.Summary):
        items    = commerce.Items()
        vouchers = commerce.Items(attribute="gift_vouchers")
        payments = commerce.Items(attribute="payment_set", item_amount_from="model.amount")


Extra
=====

In essence, an extra is simply an amount, which is added to the summary. You can also attach a name and description to this amount to help distinguish it from the others.

.. class:: rollyourown.commerce.Extra(verbose_name, amount, included, description)

All arguments are optional.

Arguments
~~~~~~~~~

.. attribute:: Extra.verbose_name

    Human readable name for this extra cost. For example, ``"VAT"`` for value added tax.

    - If you are referencing the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - If you are referencing the model instance, use ``"model.XYZ"``. If this is callable, it is called with no arguments.
    - You can also pass a callable, which is called with the model instance as an argument.

    The default value is the attribute name this ``Extra`` is assigned to, untranslated.

.. attribute:: Extra.amount

    Which attribute, field or method on each item provides the amount to be used in calculating totals.

    - If you are referencing the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - If you are referencing the model instance, use ``"model.XYZ"``. If this is callable, it is called with no arguments.
    - You can also pass a callable, which is called with the model instance as an argument.
    - You can pass an integer or decimal, which is used directly

    The default value is ``"self.get_X_amount"``, where X is the name of the summary class attribute.

.. attribute:: Extra.included

    Whether or not the value of this extra is already included in other ``Items`` or ``Extra`` elements. 
    For example, tax is often already included in the price of items, so this ``Extra`` is really just calculating how much tax is already in the total, and does not contribute its amount to any total calculations, unless explicitly removed (see :class:`Total`).

    - If you are referencing the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - If you are referencing the model instance, use ``"model.XYZ"``. If this is callable, it is called with no arguments.
    - You can also pass a callable, which is called with the model instance as an argument.
    - You can of course simply pass True or False or anything that ``bool()`` accepts

    The default value is ``False``.

.. attribute:: Extra.description

    Human readable description for this extra cost. For example, ``"19%"`` for value added tax if the law requires the relevant rate to be shown.
    This could also simply be a form of "help text".

    - If you are referencing the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - If you are referencing the model instance, use ``"model.XYZ"``. If this is callable, it is called with no arguments.
    - You can also pass a callable, which is called with the model instance as an argument.

    The default value is ``None``.

.. attribute:: Extra.editable

    When the summary is displayed as a formset (see :ref:`Summary Formsets <reference-summary-formsets>`), this field will be editable.
    
    - If ``editable`` is set to ``True``, a standard model form is used.  If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
    - If ``editable`` is set to a string, a standard form field is used for the relevant attribute. If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
    - If ``editable`` is set to a form, then this form is used. The custom form must accept a keyword argument (``instance``) and have a ``.save()`` method if the data is to be saved.

    To Customise the way forms are saved, you can overload the ``.save_form()`` method on the Summary class.

Example
~~~~~~~

::

    class MySummary(commerce.Summary):
        my_commission = commerce.Extra()
        tax           = commerce.Extra("GST", amount=get_amount_tax, description="15%", included=True)
        discount      = commerce.Extra(verbose_name="Rabatt", description="Mates Rates", amount="-12.23", included=False)

Total
=====

Totals are the output of the framework, summing together the desired `Items` and `Extra` elements.

.. class:: rollyourown.commerce.Total(*attribute_names, prevent_negative, model_cache)

All arguments are optional.

Arguments
~~~~~~~~~

.. attribute:: Total.*attribute_names

    Any positional arguments passed when defining the ``Total`` are interpreted as attribute names. 
    Each of these are names of ``Items`` elements, ``Extra`` elements or custom functions or attributes which contribute to the total in question.

    Which attribute, field or method on each item provides the amount to be used in calculating totals.

    - All strings are interpreted as referencing an ``Items``, ``Extra``, or a method or attribute on the Summary instance.
    - You can also pass a callable, which is called with the summary instance as an argument.
    - If a string begins with a minus character (for example '-tax'), then the amount is removed from the total.

    If no attribute names are given, then all ``Items`` and ``Extra`` elements are summed together for a grand total (excluding of course ``Extra`` elements that are flagged as already being included).

.. attribute:: Total.prevent_negative

    If this is true, then the final amount cannot be negative. If the total of the elements does sum to a negative value, then ``Decimal(0)`` is returned.
    By default, this is ``False``.

    Note that this argument must be given as a keyword argument.

.. attribute:: Total.model_cache

    If this value is set, the given string is taken to be the name of an attribute on the model instance which will be set to this total whenever it is calculated. If the value is set on this attribute, the calculation of a total will be skipped. The actual setting of the value is handled by the ``save_total(instance, name, field_name, total)`` template method on the ``Summary`` class, which can of course be overloaded.
    
    Note that this argument must be given as a keyword argument.

Example
~~~~~~~

::

    class MySummary(commerce.Summary):
        ...
        total   = commerce.Total()
        pretax  = commerce.Total('items', 'delivery', '-tax')
        to_pay  = commerce.Total(prevent_negative=True)


Meta Options
============

Summary classes can be annotated with meta information that helps provide context for what you are doing. These are defined in the same way Django adds meta information to its classes. The following attributes help with automated number formatting, based on the locale, currency and context. Note that if Babel_ is installed, it's extensive locale database is used, otherwise the framework falls back to using Django's own formatting, included in Django 1.2 and upwards.

    .. _babel: http://babel.edgewall.org/

Attributes
~~~~~~~~~~

.. attribute:: Summary.Meta.locale

    Locale to help provide automatic formatting of numbers. For example 9,765.34 would be 9.765,34 in Germany.     - If you are referencing the a method in the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - You can also pass a callable, which is called with the model instance as an argument.


.. attribute:: Summary.Meta.currency

    Relevant currency (eg ISO-4217 or just a symbol) for this Summary. Generally, only one currency is used for financial transactions, so the whole summary will be formatted using this currency.

    - If you are referencing the a method in the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - You can also pass a callable, which is called with the model instance as an argument.

.. attribute:: Summary.Meta.decimal_html

    The template for formatting numbers in html. This can be a string (or a callable producing a string) with placeholders for python string substitution.

    - If you are referencing the a method in the Summary instance, use ``"self.XYZ"``. If it is callable, it is called with a single argument (the model instance). 
    - You can also pass a callable, which is called with the model instance as an argument.

    A dictionary will be used with the following information available: 

    ================  ==============
    Key               Example
    ================  ==============
    ``value``         ``"1,234.56"``
    ``curr_sym``      ``"$"``
    ``decimal_sym``   ``"."``
    ``major``         ``"1,234"``
    ``minor``         ``"56"``
    ================  ==============

    The default value is::

        'span class="money">'
        '<span class="currency">%(curr_sym)s</span>%(major)s'
        '<span class="cents">%(decimal_sym)s%(minor)s</span>'
        '</span>'

    Which would produce something equivalent to:

    .. code-block:: html

        <span class="money">
          <span class="currency">$</span>123<span class="cents">.45</span>
        </span>

Example
~~~~~~~

::

    class MySummary(rollyourown.Summary):
        class Meta:
            locale = 'en-AU'
            currency = 'self.get_currency'
        
        def get_currency(self, instance):
            return instance.currency or 'AUD'

What next?
==========

Once you have defined your Summary object, you now need to be able to use it. The :ref:`Summary class usage reference <reference-commerce-usage>` will be useful here.
