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

# 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
- 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` 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:
possible: tariff system version, like `def formula_<tariffsystemversion>(...)`
- Tariff parameters:
tariff system version, tariff code, like `def formula_<tariffsystemversion>_<tariffcode>(...)` code
4. Implement the TariffSystem `formula` from [database diagram](https://redmine.c3s.cc/projects/collecting_society/wiki/Databasemodels#Invoicing)

- fomulas each formula should have the exact same plain value parameters as stated in the be one **class** method, so that parametrized calling is possible: tariff system,
they should **not** receive some tryton object, so they can be used standalone
system version
4. 5. Implement the **instance** methods
- params: sample
(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 sets; 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 utilisation_indicator = getattr(self, f'formula_{self.tariff_system.version}_{self.code}') ...

utilisation_indicators tariff_system_version = getattr(utilisation, f'{sample}_indicators') self.tariff_system.version
context_indicators tariff_code = getattr(utilisation.context, f'{sample}_indicators') self.code

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

formula = getattr(self, &lt;parametrized_tariff_system_version_and_tariff_code_formula_function&gt;)

base, relevance, adjustments = formula(**formula_indicators):
formula(indicator1, indicator2):
if update:
utilisation_indicator.save()
return (base, relevace, adjustments)

invoice_amount, distribution_amount, administration_fee admnistration_fee = \
tariff_system.calculate_invoice_amount(base, relevance, adjustments)


if update:
utilisation_indicators.base utilisation_indicator.invoice_amount = 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()

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

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

&lt;parametrized_version_formula_function&gt;)
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. 6. 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. 7. Implement a wizard to recalculate invoice amounts / utilsation indicators
7. 8. Check the wizard to write invoices ([exists](https://github.com/C3S/collecting_society/blob/development/collecting_society.py#L829) already)
8. 9. Write datasets to trigger the invoice action for the Allocations
- ensure, that some allocations are still left to invoice for manual tests

Back