diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py
index 73c378d..2d4749b 100755
--- a/modules/purchase_trade/__init__.py
+++ b/modules/purchase_trade/__init__.py
@@ -82,6 +82,14 @@ def register():
ctrm_reporting.CTRMContractPerformanceContext,
ctrm_reporting.CTRMScheduling,
ctrm_reporting.CTRMSchedulingContext,
+ ctrm_reporting.CTRMAccruals,
+ ctrm_reporting.CTRMAccrualsContext,
+ ctrm_reporting.CTRMSettlements,
+ ctrm_reporting.CTRMSettlementsContext,
+ ctrm_reporting.CTRMCashFlow,
+ ctrm_reporting.CTRMCashFlowContext,
+ ctrm_reporting.CTRMGLReconciliation,
+ ctrm_reporting.CTRMGLReconciliationContext,
configuration.Configuration,
pricing.ImportPricesStart,
pricing.ImportPricesResult,
diff --git a/modules/purchase_trade/ctrm_reporting.py b/modules/purchase_trade/ctrm_reporting.py
index 44e7005..12f2f1c 100644
--- a/modules/purchase_trade/ctrm_reporting.py
+++ b/modules/purchase_trade/ctrm_reporting.py
@@ -1545,3 +1545,442 @@ class CTRMScheduling(ModelSQL, ModelView):
Coalesce(lot.lot_qt, 0).as_('quantity'),
lot.lot_unit_line.as_('unit'),
where=where))
+
+
+class CTRMFinanceDateContextMixin:
+ from_date = fields.Date("From Date")
+ to_date = fields.Date("To Date")
+ party = fields.Many2One('party.party', "Party")
+ currency = fields.Many2One('currency.currency', "Currency")
+
+ @classmethod
+ def default_to_date(cls):
+ Date = Pool().get('ir.date')
+ return Date.today()
+
+
+class CTRMAccrualsContext(CTRMFinanceDateContextMixin, ModelView):
+ "CTRM Accruals Context"
+ __name__ = 'ctrm.reporting.finance.accruals.context'
+
+ product = fields.Many2One('product.product', "Product")
+ purchase = fields.Many2One('purchase.purchase', "Purchase")
+ sale = fields.Many2One('sale.sale', "Sale")
+ type = fields.Selection([
+ (None, ''),
+ ('physical', 'Physical'),
+ ('fee', 'Fee'),
+ ('derivative', 'Derivative'),
+ ], "Accrual Type")
+
+
+class CTRMAccruals(ModelSQL, ModelView):
+ "CTRM Accruals"
+ __name__ = 'ctrm.reporting.finance.accruals'
+
+ valuation_date = fields.Date("Valuation Date")
+ accrual_type = fields.Selection([
+ ('physical', 'Physical'),
+ ('fee', 'Fee'),
+ ('derivative', 'Derivative'),
+ ], "Accrual Type")
+ lot = fields.Many2One('lot.lot', "Lot")
+ product = fields.Many2One('product.product', "Product")
+ party = fields.Many2One('party.party', "Party")
+ purchase = fields.Many2One('purchase.purchase', "Purchase")
+ purchase_line = fields.Many2One('purchase.line', "Purchase Line")
+ sale = fields.Many2One('sale.sale', "Sale")
+ sale_line = fields.Many2One('sale.line', "Sale Line")
+ reference = fields.Char("Reference")
+ quantity = fields.Numeric("Quantity", digits=(16, 5))
+ unit = fields.Many2One('product.uom', "Unit")
+ amount = fields.Numeric("Amount", digits=(16, 2))
+ currency = fields.Many2One('currency.currency', "Currency")
+ state = fields.Char("State")
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ ValuationLine = pool.get('valuation.valuation.line')
+ Lot = pool.get('lot.lot')
+ val = ValuationLine.__table__()
+ lot = Lot.__table__()
+
+ accrual_type = Case(
+ (val.type == 'derivative', 'derivative'),
+ (val.type.in_(['line fee', 'pur. fee', 'sale fee',
+ 'shipment fee']), 'fee'),
+ else_='physical')
+ context = Transaction().context
+ where = (
+ val.type.in_(ALL_VALUATION_TYPES)
+ & ((val.lot == Null)
+ | ((lot.invoice_line == Null)
+ & (lot.sale_invoice_line == Null))))
+ if context.get('from_date'):
+ where &= val.date >= context['from_date']
+ if context.get('to_date'):
+ where &= val.date <= context['to_date']
+ if context.get('party'):
+ where &= val.counterparty == context['party']
+ if context.get('currency'):
+ where &= val.currency == context['currency']
+ if context.get('product'):
+ where &= val.product == context['product']
+ if context.get('purchase'):
+ where &= val.purchase == context['purchase']
+ if context.get('sale'):
+ where &= val.sale == context['sale']
+ if context.get('type'):
+ where &= accrual_type == context['type']
+
+ return (
+ val
+ .join(lot, 'LEFT', condition=val.lot == lot.id)
+ .select(
+ Literal(0).as_('create_uid'),
+ CurrentTimestamp().as_('create_date'),
+ Literal(None).as_('write_uid'),
+ Literal(None).as_('write_date'),
+ val.id.as_('id'),
+ val.date.as_('valuation_date'),
+ accrual_type.as_('accrual_type'),
+ val.lot.as_('lot'),
+ val.product.as_('product'),
+ val.counterparty.as_('party'),
+ val.purchase.as_('purchase'),
+ val.line.as_('purchase_line'),
+ val.sale.as_('sale'),
+ val.sale_line.as_('sale_line'),
+ val.reference.as_('reference'),
+ val.quantity.as_('quantity'),
+ val.unit.as_('unit'),
+ val.amount.as_('amount'),
+ val.currency.as_('currency'),
+ val.state.as_('state'),
+ where=where))
+
+
+class CTRMSettlementsContext(CTRMFinanceDateContextMixin, ModelView):
+ "CTRM Settlements Context"
+ __name__ = 'ctrm.reporting.finance.settlements.context'
+
+ invoice_type = fields.Selection([
+ (None, ''),
+ ('in', 'Supplier'),
+ ('out', 'Customer'),
+ ], "Invoice Type")
+ state = fields.Selection([
+ (None, ''),
+ ('draft', 'Draft'),
+ ('validated', 'Validated'),
+ ('posted', 'Posted'),
+ ('paid', 'Paid'),
+ ('cancelled', 'Cancelled'),
+ ], "State")
+ payment_status = fields.Selection([
+ (None, ''),
+ ('not', 'Not Paid'),
+ ('partial', 'Partially Paid'),
+ ('paid', 'Paid'),
+ ], "Payment Status")
+
+
+class CTRMSettlements(ModelSQL, ModelView):
+ "CTRM Settlements and Invoicing"
+ __name__ = 'ctrm.reporting.finance.settlements'
+
+ invoice = fields.Many2One('account.invoice', "Invoice")
+ invoice_type = fields.Selection([
+ ('in', 'Supplier'),
+ ('out', 'Customer'),
+ ], "Invoice Type")
+ number = fields.Char("Number")
+ reference = fields.Char("Reference")
+ invoice_date = fields.Date("Invoice Date")
+ party = fields.Many2One('party.party', "Party")
+ state = fields.Char("State")
+ currency = fields.Many2One('currency.currency', "Currency")
+ invoice_amount = fields.Numeric("Invoice Amount", digits=(16, 2))
+ paid_amount = fields.Numeric("Paid Amount", digits=(16, 2))
+ open_amount = fields.Numeric("Open Amount", digits=(16, 2))
+ payment_status = fields.Selection([
+ ('not', 'Not Paid'),
+ ('partial', 'Partially Paid'),
+ ('paid', 'Paid'),
+ ], "Payment Status")
+ move = fields.Many2One('account.move', "Move")
+ reconciliation = fields.Integer("Reconciliation")
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ Invoice = pool.get('account.invoice')
+ InvoicePayment = pool.get('account.invoice-account.move.line')
+ MoveLine = pool.get('account.move.line')
+
+ invoice = Invoice.__table__()
+ invoice_payment = InvoicePayment.__table__()
+ line = MoveLine.__table__()
+
+ amount = Coalesce(invoice.total_amount_cache, 0)
+ paid = Coalesce(Sum(line.amount_second_currency), 0)
+ open_amount = amount - paid
+ payment_status = Case(
+ (open_amount == 0, 'paid'),
+ (paid != 0, 'partial'),
+ else_='not')
+
+ context = Transaction().context
+ where = invoice.id != Null
+ if context.get('from_date'):
+ where &= invoice.invoice_date >= context['from_date']
+ if context.get('to_date'):
+ where &= invoice.invoice_date <= context['to_date']
+ if context.get('party'):
+ where &= invoice.party == context['party']
+ if context.get('currency'):
+ where &= invoice.currency == context['currency']
+ if context.get('invoice_type'):
+ where &= invoice.type == context['invoice_type']
+ if context.get('state'):
+ where &= invoice.state == context['state']
+
+ grouped = (
+ invoice
+ .join(invoice_payment, 'LEFT',
+ condition=invoice_payment.invoice == invoice.id)
+ .join(line, 'LEFT',
+ condition=line.id == invoice_payment.line)
+ .select(
+ invoice.id.as_('id'),
+ invoice.id.as_('invoice'),
+ invoice.type.as_('invoice_type'),
+ invoice.number.as_('number'),
+ invoice.reference.as_('reference'),
+ invoice.invoice_date.as_('invoice_date'),
+ invoice.party.as_('party'),
+ invoice.state.as_('state'),
+ invoice.currency.as_('currency'),
+ amount.as_('invoice_amount'),
+ paid.as_('paid_amount'),
+ open_amount.as_('open_amount'),
+ payment_status.as_('payment_status'),
+ invoice.move.as_('move'),
+ Max(line.reconciliation).as_('reconciliation'),
+ where=where,
+ group_by=[
+ invoice.id, invoice.type, invoice.number,
+ invoice.reference, invoice.invoice_date, invoice.party,
+ invoice.state, invoice.currency,
+ invoice.total_amount_cache, invoice.move,
+ ]))
+ outer_where = grouped.id != Null
+ if context.get('payment_status'):
+ outer_where &= grouped.payment_status == context['payment_status']
+
+ return grouped.select(
+ Literal(0).as_('create_uid'),
+ CurrentTimestamp().as_('create_date'),
+ Literal(None).as_('write_uid'),
+ Literal(None).as_('write_date'),
+ grouped.id.as_('id'),
+ grouped.invoice.as_('invoice'),
+ grouped.invoice_type.as_('invoice_type'),
+ grouped.number.as_('number'),
+ grouped.reference.as_('reference'),
+ grouped.invoice_date.as_('invoice_date'),
+ grouped.party.as_('party'),
+ grouped.state.as_('state'),
+ grouped.currency.as_('currency'),
+ grouped.invoice_amount.as_('invoice_amount'),
+ grouped.paid_amount.as_('paid_amount'),
+ grouped.open_amount.as_('open_amount'),
+ grouped.payment_status.as_('payment_status'),
+ grouped.move.as_('move'),
+ grouped.reconciliation.as_('reconciliation'),
+ where=outer_where)
+
+
+class CTRMCashFlowContext(CTRMFinanceDateContextMixin, ModelView):
+ "CTRM Cash Flow Context"
+ __name__ = 'ctrm.reporting.finance.cashflow.context'
+
+ overdue_only = fields.Boolean("Overdue Only")
+
+
+class CTRMCashFlow(ModelSQL, ModelView):
+ "CTRM Cash Flow Forecast"
+ __name__ = 'ctrm.reporting.finance.cashflow'
+
+ move_line = fields.Many2One('account.move.line', "Move Line")
+ move = fields.Many2One('account.move', "Move")
+ due_date = fields.Date("Due Date")
+ posting_date = fields.Date("Posting Date")
+ party = fields.Many2One('party.party', "Party")
+ account = fields.Many2One('account.account', "Account")
+ currency = fields.Many2One('currency.currency', "Currency")
+ amount = fields.Numeric("Amount", digits=(16, 2))
+ debit = fields.Numeric("Debit", digits=(16, 2))
+ credit = fields.Numeric("Credit", digits=(16, 2))
+ second_currency = fields.Many2One('currency.currency', "Second Currency")
+ amount_second_currency = fields.Numeric(
+ "Amount Second Currency", digits=(16, 2))
+ description = fields.Char("Description")
+ reference = fields.Char("Reference")
+ journal = fields.Many2One('account.journal', "Journal")
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ MoveLine = pool.get('account.move.line')
+ Move = pool.get('account.move')
+ Company = pool.get('company.company')
+
+ line = MoveLine.__table__()
+ move = Move.__table__()
+ company = Company.__table__()
+ currency = Coalesce(line.second_currency, company.currency)
+ amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
+
+ context = Transaction().context
+ where = (
+ (line.reconciliation == Null)
+ & (line.maturity_date != Null))
+ if context.get('from_date'):
+ where &= line.maturity_date >= context['from_date']
+ if context.get('to_date'):
+ where &= line.maturity_date <= context['to_date']
+ if context.get('party'):
+ where &= line.party == context['party']
+ if context.get('currency'):
+ where &= currency == context['currency']
+ if context.get('overdue_only'):
+ Date = Pool().get('ir.date')
+ where &= line.maturity_date < Date.today()
+
+ return (
+ line
+ .join(move, condition=line.move == move.id)
+ .join(company, condition=move.company == company.id)
+ .select(
+ Literal(0).as_('create_uid'),
+ CurrentTimestamp().as_('create_date'),
+ Literal(None).as_('write_uid'),
+ Literal(None).as_('write_date'),
+ line.id.as_('id'),
+ line.id.as_('move_line'),
+ line.move.as_('move'),
+ line.maturity_date.as_('due_date'),
+ move.date.as_('posting_date'),
+ line.party.as_('party'),
+ line.account.as_('account'),
+ currency.as_('currency'),
+ amount.as_('amount'),
+ line.debit.as_('debit'),
+ line.credit.as_('credit'),
+ line.second_currency.as_('second_currency'),
+ line.amount_second_currency.as_('amount_second_currency'),
+ line.description.as_('description'),
+ Coalesce(line.ext_ref, move.ext_ref).as_('reference'),
+ move.journal.as_('journal'),
+ where=where))
+
+
+class CTRMGLReconciliationContext(CTRMFinanceDateContextMixin, ModelView):
+ "CTRM GL Reconciliation Context"
+ __name__ = 'ctrm.reporting.finance.gl.context'
+
+ account = fields.Many2One('account.account', "Account")
+ lot = fields.Many2One('lot.lot', "Lot")
+ unreconciled_only = fields.Boolean("Unreconciled Only")
+
+
+class CTRMGLReconciliation(ModelSQL, ModelView):
+ "CTRM GL Reconciliation"
+ __name__ = 'ctrm.reporting.finance.gl'
+
+ move_line = fields.Many2One('account.move.line', "Move Line")
+ move = fields.Many2One('account.move', "Move")
+ posting_date = fields.Date("Posting Date")
+ journal = fields.Many2One('account.journal', "Journal")
+ account = fields.Many2One('account.account', "Account")
+ party = fields.Many2One('party.party', "Party")
+ lot = fields.Many2One('lot.lot', "Lot")
+ fee = fields.Many2One('fee.fee', "Fee")
+ invoice = fields.Many2One('account.invoice', "Invoice")
+ currency = fields.Many2One('currency.currency', "Currency")
+ debit = fields.Numeric("Debit", digits=(16, 2))
+ credit = fields.Numeric("Credit", digits=(16, 2))
+ balance = fields.Numeric("Balance", digits=(16, 2))
+ second_currency = fields.Many2One('currency.currency', "Second Currency")
+ amount_second_currency = fields.Numeric(
+ "Amount Second Currency", digits=(16, 2))
+ reconciliation = fields.Integer("Reconciliation")
+ move_state = fields.Char("Move State")
+ description = fields.Char("Description")
+ reference = fields.Char("Reference")
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ MoveLine = pool.get('account.move.line')
+ Move = pool.get('account.move')
+ Invoice = pool.get('account.invoice')
+ Company = pool.get('company.company')
+
+ line = MoveLine.__table__()
+ move = Move.__table__()
+ invoice = Invoice.__table__()
+ company = Company.__table__()
+ currency = Coalesce(line.second_currency, company.currency)
+ balance = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
+
+ context = Transaction().context
+ where = (line.lot != Null) | (line.fee != Null) | (invoice.id != Null)
+ if context.get('from_date'):
+ where &= move.date >= context['from_date']
+ if context.get('to_date'):
+ where &= move.date <= context['to_date']
+ if context.get('party'):
+ where &= line.party == context['party']
+ if context.get('currency'):
+ where &= currency == context['currency']
+ if context.get('account'):
+ where &= line.account == context['account']
+ if context.get('lot'):
+ where &= line.lot == context['lot']
+ if context.get('unreconciled_only'):
+ where &= line.reconciliation == Null
+
+ return (
+ line
+ .join(move, condition=line.move == move.id)
+ .join(company, condition=move.company == company.id)
+ .join(invoice, 'LEFT', condition=invoice.move == move.id)
+ .select(
+ Literal(0).as_('create_uid'),
+ CurrentTimestamp().as_('create_date'),
+ Literal(None).as_('write_uid'),
+ Literal(None).as_('write_date'),
+ line.id.as_('id'),
+ line.id.as_('move_line'),
+ line.move.as_('move'),
+ move.date.as_('posting_date'),
+ move.journal.as_('journal'),
+ line.account.as_('account'),
+ line.party.as_('party'),
+ line.lot.as_('lot'),
+ line.fee.as_('fee'),
+ invoice.id.as_('invoice'),
+ currency.as_('currency'),
+ line.debit.as_('debit'),
+ line.credit.as_('credit'),
+ balance.as_('balance'),
+ line.second_currency.as_('second_currency'),
+ line.amount_second_currency.as_('amount_second_currency'),
+ line.reconciliation.as_('reconciliation'),
+ move.state.as_('move_state'),
+ line.description.as_('description'),
+ Coalesce(line.ext_ref, move.ext_ref).as_('reference'),
+ where=where))
diff --git a/modules/purchase_trade/ctrm_reporting.xml b/modules/purchase_trade/ctrm_reporting.xml
index 1d3482f..e78941b 100644
--- a/modules/purchase_trade/ctrm_reporting.xml
+++ b/modules/purchase_trade/ctrm_reporting.xml
@@ -240,6 +240,86 @@