Datenbank #1140

Updated by Alexander Blum about 1 year ago

# Background

- `Invoice.amount/shared_amount` are only helper attributes for testing the invoicing, should be replaced by
`UtilisationIndicators.invoice_amount` = `UtilizationIndicators.administration_fee` + `UtilizationIndicators.distribution_amount`
- relationship see also [database diagram](https://redmine.c3s.cc/projects/collecting_society/wiki/Databasemodels#Invoicing)
- use `erpserver> db-console` for a tryton console to test the functions; maybe write a script with some objects initialized and import it there

>>> Tariff = pool.get('tariff_system.tariff')
>>> tariff = Tariff(1)

# Procedure

1. Write a [wizard](https://github.com/C3S/collecting_society/blob/development/collecting_society.py#L829) to trigger the allocation process
- can be triggered by
- cron job
- manual [action](https://github.com/C3S/collecting_society/blob/development/collecting_society.xml#L800)
- in Declaration Menu (action in entry view or on [selected](https://github.com/C3S/collecting_society/blob/development/collecting_society.py#L3973) items in list view)
- in Utilization Menu (action in entry view or on [selected](https://github.com/C3S/collecting_society/blob/development/collecting_society.py#L3973) items in list view). Maybe we could skip that, but probable it's a valid usecase, like:
- if some utilization (tariff bound!) of a declaration should be payed for and some other has issues to be resolved first
- or a multi day festival event and the utilisations should be split.
- creates allocations with all corresponding utilisations connected, one allocation for each licensee
- loops over different sets of declarations depending on where/how the action was triggered (algorithm should be generic, see next point confirm screen how this is done):
- declarations list with no selection: all suitable declarations with suitable utilisations (default for the cron job e.g. each day)
- declarations list with selection: all suitable declaration.utilisations for each selected suitable declaration
- declaration entry: all suitable declaration.utilisations for this declaration (if suitable)
- utilisations list with no selection: all suitable selected utilisations
- utilisations list with selection: all suitable utilisations in list
- utilisation entry: this utilisation (if suitable)
- confirm screen (like in [fingerprint_merge](https://github.com/C3S/collecting_society/blob/development/collecting_society.py#L4194), maybe a tree list with declarations -> utilisations
- list of definitely selected utilisations/declarations
- info on deselection of non suitable utilisations/declarations
2. Write a dataset [triggering](https://github.com/C3S/collecting_society_docker/blob/development/volumes/shared/data/datasets
/device_message_fingerprint_merge.py#L29) the wizard for some Declarations

- ensure, that non complete utilisation datasets for manual tests are still available
2. Write a dataset [triggering](https://github.com/C3S/collecting_society_docker/blob/development/volumes/shared/data/datasets/device_message_fingerprint_merge.py#L29) the wizard for some Declarations

3. Implement the Tariff and TariffSystem `formula`s from [database diagram](https://redmine.c3s.cc/projects/collecting_society/wiki/Databasemodels#Invoicing)
- each formula should be one **class** method, so that parametrized calling is possible
- TariffSystem parametes: tariff system version; like

@classmethod
def formula_<tariffsystemversion>(...)
- Tariff parameters: tariff code (includes version already); one for each tariff; like

@classmethod
def formula_<tariffcode>(...)
- fomulas should have the exact same plain value parameters as stated in the tariff system (check the latest version),
they should **not** receive some tryton object, so they can be used standalone
4. Implement the **instance** methods
- params: sample = indicator set, maybe also 'all' to calculate all indicator sets, but then the returned tuples have to be e.g. a dict with sample name as key and the tuple as value update = write or just calculate)
- maybe rename `calculate_invoice_amount`, as all values are returned, or maybe better split invoice amount calculation (only invoice_amount returned) from calculating the distribution_amount and administration_fee (but not sure if this is needed by some other process)
- `Allocation.calculate_invoice_amount(self, sample, update=False)`

invoice_amount = 0
distribution_amount = 0
administration_fee = 0
for utilisation self.utilisations:
ia, da, af = utilisation.calculate_invoice_amount(sample, update)
invoice_amount += ia
distribution_amount += da
administration_fee += af
return invoice_amount, distribution_amount, administration_fee
- `Utilisation.calculate_invoice_amount(self, sample, update=False)`

utilisation = self
invoice_amount = self.tariff.calculate_invoice_amount(utilisation, sample, update)
return invoice_amount, distribution_amount, admnistration_fee
- `Tariff.calculate_invoice_amount(self, utilisation, sample, update=False)`

formula = getattr(self, f'formula_{self.tariff_system.version}_{self.code}')

utilisation_indicators = getattr(utilisation, f'{sample}_indicators')
context_indicators = getattr(utilisation.context, f'{sample}_indicators')

# generic way to get all and only indicators needed for all different formulas
import inspect # on top
formula_indicators = {
field: getattr(context_indicators, field)
for field in inspect.signature(formula).parameters
}

base, relevance, adjustments = formula(**formula_indicators):
invoice_amount, distribution_amount, administration_fee = \
tariff_system.calculate_invoice_amount(base, relevance, adjustments)

if update:
utilisation_indicators.base = base
utilisation_indicators.relevance = relevance
utilisation_indicators.adjustments = adjustments
utilisation_indicators.invoice_amount = invoice_amount
utilisation_indicators.distribution_amount = distribution_amount
utilisation_indicators.administration_fee = administration_fee
utilisation_indicators.save()

return invoice_amount, distribution_amount, administration_fee
- `TariffSystem.calculate_invoice_amount(self, base, relevance, adjustments)`

formula = getattr(self, f'formula_{self.version}')

invoice_amount = formula(base, relevance, adjustments)
distribution_amount = invoice_amount * self.administration_share
administration_fee = invoice_amount - distribution_amount
# check if results add up

return invoice_amount, distribution_amount, administration_fee

5. Implement the same for `calculate_utilisation_indicators()`, maybe reuse in `calculate_invoice_amount()`
- `Utilisation.calculate_utilisation_indicators(self, sample, update)`
- `Tariff.calculate_utilisation_indicators(self, utilisation, sample, update)`
6. Implement a wizard to recalculate invoice amounts / utilsation indicators
7. Check the wizard to write invoices ([exists](https://github.com/C3S/collecting_society/blob/development/collecting_society.py#L829) already)
8. Write datasets to trigger the invoice action for the Allocations
- ensure, that some allocations are still left to invoice for manual tests

Back