# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from trytond.model import fields, Workflow from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, Id from trytond.model import (ModelSQL, ModelView) from trytond.tools import is_full_text, lstrip_wildcard from trytond.transaction import Transaction, inactive_records from decimal import getcontext, Decimal, ROUND_HALF_UP from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr from sql.conditionals import Case from sql import Column, Literal from sql.functions import CurrentTimestamp, DateTrunc from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction from itertools import chain, groupby from operator import itemgetter import datetime from collections import defaultdict from sql import Table from trytond.modules.purchase_trade.service import ContractFactory import requests import io import base64 import logging import json import re import html from trytond.exceptions import UserError from trytond.modules.stock.shipment import SupplierShipping as BaseSupplierShipping logger = logging.getLogger(__name__) CHARTER_LAYTIME_EVENT_SELECTION = [ (None, ''), ('notice_of_readiness', 'Notice of Readiness'), ('arrival', 'Arrival'), ('all_fast', 'All Fast'), ('hoses_connected', 'Hoses Connected'), ('start_pumping', 'Start Pumping'), ('end_pumping', 'End Pumping'), ('completed_loading', 'Completed Loading'), ('completed_discharge', 'Completed Discharge'), ('hoses_disconnected', 'Hoses Disconnected'), ('documents_on_board', 'Documents on Board'), ('sailing', 'Sailing'), ] class Location(metaclass=PoolMeta): __name__ = 'stock.location' def get_places(self): t = Table('places') cursor = Transaction().connection.cursor() cursor.execute(*t.select( t.PLACE_KEY, where=t.PLACE_NAME.ilike(f'%{self.name}%') )) rows = cursor.fetchall() if rows: return int(rows[0][0]) @classmethod def getLocationByName(cls, location, type): location = location.upper() loc = cls.search([('name', '=', location),('type', '=', type)], limit=1) if loc: return loc[0].id else: loc = cls() loc.name = location loc.type = type cls.save([loc]) return loc @classmethod def get_transit_id(cls): return cls.getLocationByName('TRANSIT','storage') def is_transit(self): if self.name == 'Transit': return True class Move(metaclass=PoolMeta): __name__ = 'stock.move' bldate = fields.Date("BL date") blnumber = fields.Char("BL number") lotqt = fields.One2Many('lot.qt','lot_move',"Lots") lot = fields.Many2One('lot.lot',"Lot") trade_purchase = fields.Function( fields.Many2One('purchase.purchase', "Purchase"), 'get_trade_purchase') trade_sale = fields.Function( fields.Many2One('sale.sale', "Sale"), 'get_trade_sale') trade_purchase_reference = fields.Function( fields.Char("Purchase Ref."), 'get_trade_purchase_reference') trade_sale_reference = fields.Function( fields.Char("Sale Ref."), 'get_trade_sale_reference') def _trade_purchase_line(self): return getattr(getattr(self, 'lot', None), 'line', None) def _trade_sale_line(self): return getattr(getattr(self, 'lot', None), 'sale_line', None) @staticmethod def _contract_reference(contract): if not contract: return None return ( getattr(contract, 'reference', None) or getattr(contract, 'our_reference', None)) def get_trade_purchase(self, name): line = self._trade_purchase_line() purchase = getattr(line, 'purchase', None) return purchase.id if purchase else None def get_trade_sale(self, name): line = self._trade_sale_line() sale = getattr(line, 'sale', None) return sale.id if sale else None def get_trade_purchase_reference(self, name): line = self._trade_purchase_line() return self._contract_reference(getattr(line, 'purchase', None)) def get_trade_sale_reference(self, name): line = self._trade_sale_line() return self._contract_reference(getattr(line, 'sale', None)) def get_linked_transit_move(self): if self.from_location.is_transit(): Move = Pool().get('stock.move') Location = Pool().get('stock.location') moves = Move.search([('lot','=',self.lot),('to_location','=',Location.get_transit_id())],order=[('id', 'DESC')],limit=1) return moves[0] if moves else None @classmethod def validate(cls, moves): super(Move, cls).validate(moves) Lot = Pool().get('lot.lot') LotMove = Pool().get('lot.move') for move in moves: if move.lot: l = Lot(move.lot.id) lm = LotMove.search([('lot','=',l.id),('move','=',move)]) if len(lm) == 0: lm = LotMove.search(['lot','>',0],order=[('sequence','DESC')]) new_lm = LotMove() new_lm.move = move.id new_lm.lot = l.id new_lm.sequence = 1 if lm: new_lm.sequence = lm[0].sequence + 1 LotMove.save([new_lm]) class InvoiceLine(metaclass=PoolMeta): __name__ = 'account.invoice.line' lot = fields.Many2One('lot.lot',"Lot") fee = fields.Many2One('fee.fee',"Fee") included_padding = fields.Function( fields.Numeric("Inc. padding", digits=(1, 5)), 'get_included_padding') def get_included_padding(self, name): if (self.lot and self.invoice_type == 'out' and self.description == 'Pro forma' and Decimal(str(self.quantity or 0)) > 0): return self.lot.sale_invoice_padding or Decimal(0) return Decimal(0) @classmethod def validate(cls, lines): super(InvoiceLine, cls).validate(lines) Lot = Pool().get('lot.lot') for line in lines: if line.lot and line.quantity > 0: l = Lot(line.lot.id) if line.description == 'Pro forma': if line.invoice_type == 'in': l.invoice_line_prov = line.id l.lot_pur_inv_state = 'prov' else: l.sale_invoice_line_prov = line.id l.lot_sale_inv_state = 'prov' elif line.description == 'Final': if line.invoice_type == 'in': l.invoice_line = line.id l.lot_pur_inv_state = 'invoiced' else: l.sale_invoice_line = line.id l.lot_sale_inv_state = 'invoiced' Lot.save([l]) class ShipmentInternal(metaclass=PoolMeta): __name__ = 'stock.shipment.internal' fees = fields.One2Many('fee.fee','shipment_internal',"Fees") sof_json = [ { "date": "2025-02-02", "time": "19:00", "event": "S/B FOR ARRIVAL - PROCEED TO CAK NO.2 ANCHORAGE", "end_time": "2025-02-02 21:30", "duration": {"iso_8601": "PT26H30M", "text": "2h 30m"}, }, { "date": "2025-02-02", "time": "21:30", "event": "DROP ANCHOR AT CAK NO.2 ANCHOR / ARR. CAN NOR TENDERED", "end_time": "2025-02-02 21:40", "duration": {"iso_8601": "-PT23H50M", "text": "10m"}, }, { "date": "2025-02-02", "time": "21:40", "event": "BROUGHT UP ANCHOR/F.W.E", "end_time": "2025-02-03 07:20", "duration": {"iso_8601": "PT9H40M", "text": "9h 40m"}, }, { "date": "2025-02-03", "time": "07:20", "event": "S.B.E. FOR SHIFTING", "end_time": "2025-02-03 07:45", "duration": {"iso_8601": "PT0H25M", "text": "25m"}, }, { "date": "2025-02-03", "time": "07:45", "event": "ANCHOR AWEIGHT", "end_time": "2025-02-03 10:45", "duration": {"iso_8601": "PT3H0M", "text": "3h"}, }, { "date": "2025-02-03", "time": "10:45", "event": "SEA PILOT ON BOARD", "end_time": "2025-02-03 14:00", "duration": {"iso_8601": "PT3H15M", "text": "3h 15m"}, }, { "date": "2025-02-03", "time": "14:00", "event": "EXCHANGED SEA PILOT TO RIVER PILOT", "end_time": "2025-02-03 17:45", "duration": {"iso_8601": "PT3H45M", "text": "3h 45m"}, }, { "date": "2025-02-03", "time": "17:45", "event": "DROP ANCHOR AT NANTONS DANGEROUS ANCHORAGE", "end_time": "2025-02-03 18:00", "duration": {"iso_8601": "PT0H15M", "text": "15m"}, }, { "date": "2025-02-03", "time": "18:00", "event": "BROUGHT UP ANCHOR/F.W.E/PILOT OFF", "end_time": "2025-02-04 06:30", "duration": {"iso_8601": "PT12H30M", "text": "12h 30m"}, }, { "date": "2025-02-04", "time": "06:30", "event": "S.B.E. FOR BERTHING", "end_time": "2025-02-04 07:10", "duration": {"iso_8601": "PT0H40M", "text": "40m"}, }, { "date": "2025-02-04", "time": "07:10", "event": "PILOT ON BOARD", "end_time": "2025-02-04 07:15", "duration": {"iso_8601": "PT0H5M", "text": "5m"}, }, { "date": "2025-02-04", "time": "07:15", "event": "ANCHOR AWEIGHT", "end_time": "2025-02-04 09:20", "duration": {"iso_8601": "PT2H5M", "text": "2h 5m"}, }, { "date": "2025-02-04", "time": "09:20", "event": "FWD TUG MADE FAST", "end_time": "2025-02-04 09:35", "duration": {"iso_8601": "PT0H15M", "text": "15m"}, }, { "date": "2025-02-04", "time": "09:35", "event": "FIRST LINE ASHORE", "end_time": "2025-02-04 08:50", "duration": { "iso_8601": "-PT0H45M", "text": "Incohérence temporelle", "alert": "Vérifier l'heure de TUG OFF", }, }, { "date": "2025-02-04", "time": "08:50", "event": "TUG OFF", "end_time": "2025-02-04 09:55", "duration": {"iso_8601": "PT1H5M", "text": "1h 5m"}, }, { "date": "2025-02-04", "time": "09:55", "event": "ALL LINE MADE FAST/F.W.E / NOR ACCEPTED", "end_time": "2025-02-04 10:15", "duration": {"iso_8601": "PT0H20M", "text": "20m"}, }, { "date": "2025-02-04", "time": "10:15", "event": "GANGWAY DOWN/PILOT OFF", "end_time": "2025-02-04 10:20", "duration": {"iso_8601": "PT0H5M", "text": "5m"}, }, { "date": "2025-02-04", "time": "10:20", "event": "AGENT / BAMBRIATION ON BOARD", "end_time": "2025-02-04 11:10", "duration": {"iso_8601": "PT0H50M", "text": "50m"}, }, { "date": "2025-02-04", "time": "11:10", "event": "FREE-PRACTIQUE QUANTED", "end_time": "2025-02-04 11:40", "duration": {"iso_8601": "PT0H30M", "text": "30m"}, }, { "date": "2025-02-04", "time": "11:40", "event": "SURVEYOR & LOADING MASTER ON BOARD", "end_time": "2025-02-04 11:40", "duration": { "iso_8601": "PT0H0M", "text": "0m", "note": "Début de SAFETY MEETING", }, }, { "date": "2025-02-04", "time": "11:40", "event": "SAFETY MEETING", "end_time": "2025-02-04 12:20", "duration": {"iso_8601": "PT0H40M", "text": "40m"}, }, { "date": "2025-02-04", "time": "11:45", "event": "TANK INSPECTION", "end_time": "2025-02-04 12:30", "duration": {"iso_8601": "PT0H45M", "text": "45m"}, }, { "date": "2025-02-04", "time": "13:10", "event": 'CARGO HOSE CONNECTED (3 x 8")', "end_time": "2025-02-04 13:25", "duration": {"iso_8601": "PT0H15M", "text": "15m"}, }, { "date": "2025-02-04", "time": "13:25", "event": "LEAKAGE TESTED", "end_time": "2025-02-04 13:45", "duration": {"iso_8601": "PT0H20M", "text": "20m"}, }, { "date": "2025-02-04", "time": "14:15", "event": "COMMENCED LOADING SULPHURIC ACID", "end_time": "2025-02-05 08:50", "duration": {"iso_8601": "PT18H35M", "text": "18h 35m"}, }, { "date": "2025-02-05", "time": "08:50", "event": "COMPLETED LOADING SULPHURIC ACID", "end_time": "2025-02-05 09:00", "duration": {"iso_8601": "PT0H10M", "text": "10m"}, }, { "date": "2025-02-05", "time": "08:50", "event": "AIR BLOWING", "end_time": "2025-02-05 09:00", "duration": {"iso_8601": "PT0H10M", "text": "10m"}, }, { "date": "2025-02-05", "time": "09:00", "event": "VILLAGING, SAMPLING AND CALCULATION", "end_time": "2025-02-05 11:00", "duration": {"iso_8601": "PT2H0M", "text": "2h"}, }, { "date": "2025-02-05", "time": "10:40", "event": 'HOSE OFF (308")', "end_time": "2025-02-05 11:40", "duration": {"iso_8601": "PT1H0M", "text": "1h"}, }, { "date": "2025-02-05", "time": "11:40", "event": "CARGO DOCUMENT COMPLETED", "end_time": "2025-02-05 11:40", "duration": {"iso_8601": "PT0H0M", "text": "Événement final"}, }, ] class ContainerType(ModelSQL, ModelView): "Container Type" __name__ = 'stock.container.type' code = fields.Char('Code', required=True) name = fields.Char('Description', required=True) teu = fields.Numeric('TEU Factor', digits=(5, 2), required=True) is_reefer = fields.Boolean('Reefer') is_special = fields.Boolean('Special Equipment') class ShipmentContainer(ModelSQL, ModelView): "Shipment Container" __name__ = 'stock.shipment.container' shipment = fields.Many2One( 'stock.shipment.in', 'Shipment', required=True ) container_type = fields.Many2One( 'stock.container.type', 'Container Type', required=True ) container_no = fields.Char('Container Number') quantity = fields.Integer('Quantity', required=True) seal_no = fields.Char('Seal Number') is_reefer = fields.Boolean('Reefer') class ShipmentWR(ModelSQL,ModelView): "Shipment WR" __name__ = "shipment.wr" shipment_in = fields.Many2One('stock.shipment.in',"Shipment In") wr = fields.Many2One('weight.report',"WR") RATE_BASIS = [ (None, ''), ('per_day', 'Per Day'), ('per_hour', 'Per Hour'), ('mt_per_hour', 'MT/hour'), ('per_mt', 'Per MT'), ('per_wmt', 'Per WMT'), ('per_dmt', 'Per DMT'), ('per_cbm', 'Per CBM'), ('per_lot', 'Per Lot'), ('lumpsum', 'Lumpsum'), ('percent', 'Percent'), ('other', 'Other'), ] class CharterRateType(ModelSQL, ModelView): "Charter Rate Type" __name__ = 'charter.rate.type' name = fields.Char("Name", required=True) code = fields.Char("Code") category = fields.Selection([ (None, ''), ('demurrage', 'Demurrage'), ('despatch', 'Despatch'), ('pumping', 'Pumping'), ('freight', 'Freight'), ('detention', 'Detention'), ('waiting', 'Waiting'), ('shifting', 'Shifting'), ('heating', 'Heating'), ('cleaning', 'Cleaning'), ('bunker', 'Bunker'), ('port_cost', 'Port Cost'), ('other', 'Other'), ], "Category") default_basis = fields.Selection(RATE_BASIS, "Default Basis") active = fields.Boolean("Active") @staticmethod def default_active(): return True class CharterConditionRate(ModelSQL, ModelView): "Charter Condition Rate" __name__ = 'charter.condition.rate' condition = fields.Many2One( 'charter.condition', "Condition", required=True, ondelete='CASCADE') rate_type = fields.Many2One('charter.rate.type', "Type") description = fields.Char("Description") rate = fields.Numeric("Rate", digits=(16, 6)) currency = fields.Many2One('currency.currency', "Curr.") basis = fields.Selection(RATE_BASIS, "Basis") applies_to = fields.Selection([ (None, ''), ('load', 'Load'), ('discharge', 'Discharge'), ('both', 'Both'), ('waiting', 'Waiting'), ('pumping', 'Pumping'), ('shifting', 'Shifting'), ('voyage', 'Voyage'), ('other', 'Other'), ], "Applies To") minimum = fields.Numeric("Min", digits=(16, 6)) maximum = fields.Numeric("Max", digits=(16, 6)) reference = fields.Text("Reference") @fields.depends('rate_type') def on_change_rate_type(self): if self.rate_type and self.rate_type.default_basis: self.basis = self.rate_type.default_basis class CharterConditionLaytimeStartRule(ModelSQL, ModelView): "Charter Condition Laytime Start Rule" __name__ = 'charter.condition.laytime.start.rule' condition = fields.Many2One( 'charter.condition', "Condition", required=True, ondelete='CASCADE') sequence = fields.Integer("Sequence") event = fields.Selection( CHARTER_LAYTIME_EVENT_SELECTION, "Event", required=True) offset = fields.Numeric("Offset", digits=(16, 4)) offset_unit = fields.Selection([ (None, ''), ('hours', 'Hours'), ('days', 'Days'), ], "Offset Unit") @staticmethod def default_sequence(): return 10 @staticmethod def default_offset_unit(): return 'hours' class CharterCondition(ModelSQL, ModelView): "Charter Condition" __name__ = 'charter.condition' _party_role_selection = [ (None, ''), ('owner', 'Owner'), ('charterer', 'Charterer'), ('supplier', 'Supplier'), ('customer', 'Customer'), ('broker', 'Broker'), ('agent', 'Agent'), ('terminal', 'Terminal'), ('other', 'Other'), ] name = fields.Char("Name") charter_party = fields.Many2One( 'stock.charter.party', "Charter Party", ondelete='CASCADE') purchase = fields.Many2One( 'purchase.purchase', "Purchase", ondelete='CASCADE') purchase_line = fields.Many2One( 'purchase.line', "Purchase Line", ondelete='CASCADE') party = fields.Many2One('party.party', "Party") party_role = fields.Selection( _party_role_selection, "Party Role") responsibility = fields.Selection([ (None, ''), ('ours', 'Ours'), ('counterparty', 'Counterparty'), ('shared', 'Shared'), ('pass_through', 'Pass-through'), ('owner', 'Owner'), ('charterer', 'Charterer'), ], "Responsibility") laytime_clause = fields.Text("Laytime Clause") laytime_allowed = fields.Numeric("Laytime Allowed", digits=(16, 4)) laytime_unit = fields.Selection([ (None, ''), ('hours', 'Hours'), ('days', 'Days'), ('wwd', 'Weather Working Days'), ('wwd_shex', 'WWD SHEX'), ('wwd_shinc', 'WWD SHINC'), ('running_hours', 'Running Hours'), ], "Laytime Unit") laytime_start = fields.Text("Laytime Start") laytime_end = fields.Text("Laytime End") nor_clause = fields.Text("NOR Clause") laytime_start_rule = fields.Selection([ ('simple', 'Single Event + Offset'), ('earliest_of', 'Earliest Of'), ('latest_of', 'Latest Of'), ], "Laytime Start Rule") laytime_start_event = fields.Selection( CHARTER_LAYTIME_EVENT_SELECTION, "Laytime Start Event") laytime_start_offset = fields.Numeric( "Laytime Start Offset", digits=(16, 4)) laytime_start_offset_unit = fields.Selection([ (None, ''), ('hours', 'Hours'), ('days', 'Days'), ], "Laytime Start Offset Unit") laytime_start_rules = fields.One2Many( 'charter.condition.laytime.start.rule', 'condition', "Laytime Start Candidates") laytime_end_event = fields.Selection( CHARTER_LAYTIME_EVENT_SELECTION, "Laytime End Event") turn_time = fields.Numeric("Turn Time", digits=(16, 4)) turn_time_unit = fields.Selection([ (None, ''), ('hours', 'Hours'), ('days', 'Days'), ], "Turn Time Unit") reversible = fields.Boolean("Reversible Laytime") all_time_saved = fields.Boolean("All Time Saved") demurrage_clause = fields.Text("Demurrage Clause") despatch_clause = fields.Text("Despatch Clause") exceptions = fields.Text("Exceptions") remarks = fields.Text("Remarks") rates = fields.One2Many( 'charter.condition.rate', 'condition', "Rates") @staticmethod def default_reversible(): return False @staticmethod def default_all_time_saved(): return False @staticmethod def default_laytime_start_rule(): return 'simple' @staticmethod def default_laytime_start_event(): return 'notice_of_readiness' @staticmethod def default_laytime_start_offset_unit(): return 'hours' @staticmethod def default_laytime_end_event(): return 'hoses_disconnected' @staticmethod def default_party_role(): role_context = Transaction().context.get('charter_condition_role') if role_context in {'purchase', 'purchase_line', 'supplier'}: return 'supplier' if role_context in {'sale', 'sale_line', 'customer'}: return 'customer' if role_context == 'charter_party': return 'owner' @fields.depends( 'charter_party', 'purchase', 'purchase_line', 'sale', 'sale_line') def get_party_roles(self): role_context = Transaction().context.get('charter_condition_role') if (role_context in {'purchase', 'purchase_line', 'supplier'} or getattr(self, 'purchase', None) or getattr(self, 'purchase_line', None)): return [(None, ''), ('supplier', 'Supplier')] if (role_context in {'sale', 'sale_line', 'customer'} or getattr(self, 'sale', None) or getattr(self, 'sale_line', None)): return [(None, ''), ('customer', 'Customer')] if role_context == 'charter_party' or getattr( self, 'charter_party', None): return [ item for item in self._party_role_selection if item[0] not in {'supplier', 'customer'}] return self._party_role_selection @classmethod def validate(cls, conditions): super().validate(conditions) for condition in conditions: allowed_roles = {role for role, _ in condition.get_party_roles()} if condition.party_role not in allowed_roles: raise UserError( "This charter condition party role is not allowed " "for its source.") class CharterParty(ModelSQL, ModelView): "Charter Party" __name__ = 'stock.charter.party' name = fields.Char("Name", required=True) reference = fields.Char("Reference") active = fields.Boolean("Active") company = fields.Many2One('company.company', "Company") charter_type = fields.Selection([ (None, ''), ('voyage', 'Voyage Charter'), ('time', 'Time Charter'), ('coa', 'Contract of Affreightment'), ('bareboat', 'Bareboat Charter'), ], "Charter Type", required=True) owner = fields.Many2One('party.party', "Owner") charterer = fields.Many2One('party.party', "Charterer") broker = fields.Many2One('party.party', "Broker") vessel = fields.Many2One('trade.vessel', "Vessel") product = fields.Many2One('product.product', "Product") load_port = fields.Many2One('stock.location', "Load Port") discharge_port = fields.Many2One('stock.location', "Discharge Port") from_location = fields.Many2One('stock.location', "From Location") to_location = fields.Many2One('stock.location', "To Location") laycan_from = fields.Date("Laycan From") laycan_to = fields.Date("Laycan To") contract_date = fields.Date("Contract Date") currency = fields.Many2One('currency.currency', "Currency") governing_law = fields.Char("Governing Law") jurisdiction = fields.Char("Jurisdiction") document = fields.Many2One('document.incoming', "Document") notes = fields.Text("Notes") conditions = fields.One2Many( 'charter.condition', 'charter_party', "Conditions") @staticmethod def default_active(): return True class ShipmentIn(metaclass=PoolMeta): __name__ = 'stock.shipment.in' from_location = fields.Many2One( 'stock.location', 'From location', domain=[('type', '!=', 'customer')]) to_location = fields.Many2One( 'stock.location', 'To location', domain=[('type', '!=', 'supplier')]) shipment_type = fields.Function(fields.Selection([ (None, ''), ('dropship', 'Dropship'), ('inbound', 'Inbound'), ], "Shipment Type"), 'get_shipment_type') transport_type = fields.Selection([ ('vessel', 'Vessel'), ('truck', 'Truck'), ('other', 'Other'), ], 'Transport type') vessel = fields.Many2One('trade.vessel', "Vessel", states={'invisible': Eval('transport_type') == 'truck'}) info = fields.Function(fields.Text("Info",states={'invisible': ~Eval('info',False)}),'get_info') anim = fields.Function(fields.Text(""),'get_anim') carte = fields.Function(fields.Text("",states={'invisible': ~Eval('info',False)}),'get_imo') fees = fields.One2Many('fee.fee','shipment_in',"Fees") pnl_lines = fields.Function( fields.One2Many('valuation.valuation.line', '', "Pnl"), 'get_pnl_lines') lotqt = fields.One2Many('lot.qt','lot_shipment_in',"Lots") quantity = fields.Function(fields.Numeric("Quantity"),'get_quantity') unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit') bl_date = fields.Date("BL date") bl_number = fields.Char("BL number") etl = fields.Date("Est. Loading") eta = fields.Date("🏗️ETA POL") etad = fields.Date("⚓ETA") atad = fields.Date("Act. Destination") etd = fields.Date("ETD") unloaded = fields.Date("Act. Discharge") booking = fields.Char("Booking Nb") booking_date =fields.Date("Booking date") ref = fields.Char("Our reference") note = fields.Text("Notes") dashboard = fields.Many2One('purchase.dashboard',"Dashboard") himself = fields.Function(fields.Many2One('stock.shipment.in',"Shipment"),'get_sh') charter_party = fields.Many2One('stock.charter.party', "Charter Party") owner_charter_conditions = fields.Function( fields.One2Many('charter.condition', '', "Owner Conditions"), 'get_owner_charter_conditions') purchase_charter_conditions = fields.Function( fields.One2Many('charter.condition', '', "Supplier Conditions"), 'get_purchase_charter_conditions') sale_charter_conditions = fields.Function( fields.One2Many('charter.condition', '', "Customer Conditions"), 'get_sale_charter_conditions') sof = fields.One2Many('sof.statement', 'shipment',"Demurrage calculations") del_from = fields.Date("Shipment period from") del_to = fields.Date("to") estimated_date = fields.One2Many('pricing.estimated','shipment_in',"Estimated date") carrier_ = fields.Many2One('party.party',"Carrier") operator = fields.Many2One( 'party.party', "Operator", domain=[('categories.name', '=', 'OPERATOR')]) start_date = fields.Date("Start date") travel_nb = fields.Char("Travel nb") receive_date = fields.Date("Reception date") receive_nb = fields.Char("Reception nb") cargo_mode = fields.Selection([ ('none', ''), ('bulk', 'Bulk'), ('container', 'Container'), ('tanker', 'Tanker'), ], 'Cargo Mode', required=True, states={'invisible': Eval('transport_type') == 'truck'}) vessel_type = fields.Function( fields.Many2One('stock.vessel.type', "Vessel type", states={'invisible': Eval('transport_type') == 'truck'}), 'get_vessel_type') container = fields.One2Many( 'stock.shipment.container', 'shipment', 'Container' ) shipment_wr = fields.One2Many('shipment.wr','shipment_in',"WR") controller = fields.Many2One('party.party',"Controller") surveyor = fields.Many2One('party.party', "Surveyor") controller_target = fields.Char("Targeted controller") send_instruction = fields.Boolean("Send instruction") instructions = fields.Text("Instructions") add_bl = fields.Boolean("Add BL") add_invoice = fields.Boolean("Add invoice") returned_id = fields.Char("Returned ID") result = fields.Char("Result",readonly=True) agent = fields.Many2One('party.party',"Booking Agent") service_order_key = fields.Integer("Service Order Key") @classmethod def __setup__(cls): super().__setup__() cls._transitions |= set(( ('started', 'cancelled'), )) cls._buttons.update({ 'compute': {}, 'send': {}, }) @staticmethod def _normalize_transport_values(values, record=None): values = dict(values) transport_type = values.get( 'transport_type', getattr(record, 'transport_type', None)) if transport_type == 'truck': values['vessel'] = None values['cargo_mode'] = 'none' elif values.get('cargo_mode') == 'none': values['cargo_mode'] = 'bulk' return values @classmethod def create(cls, vlist): return super().create([ cls._normalize_transport_values(values) for values in vlist]) @classmethod def write(cls, *args): new_args = cls._normalize_transport_write_args(args) if not new_args: return return super().write(*new_args) @classmethod def _normalize_transport_write_args(cls, args): new_args = [] for index in range(0, len(args), 2): records = args[index] values = args[index + 1] for record in records: new_args.extend([ [record], cls._normalize_transport_values(values, record)]) return new_args @classmethod def _stock_move_origin(cls, move): return 'stock.move,%s' % move.id @classmethod def _delete_draft_account_moves_for_stock_moves(cls, moves): moves = [move for move in moves if getattr(move, 'id', None)] if not moves: return AccountMove = Pool().get('account.move') account_moves = AccountMove.search([ ('origin', 'in', [cls._stock_move_origin(move) for move in moves]), ]) posted = [ move for move in account_moves if getattr(move, 'state', None) == 'posted'] if posted: names = ', '.join(move.rec_name for move in posted[:5]) if len(posted) > 5: names += '...' raise UserError( "Cannot cancel started shipment because stock accounting " "move(s) are already posted: %s" % names) draft_moves = [ move for move in account_moves if getattr(move, 'state', None) != 'posted'] if draft_moves: AccountMove.delete(draft_moves) @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, shipments): moves = [ move for shipment in shipments for move in shipment.incoming_moves + shipment.inventory_moves] cls._delete_draft_account_moves_for_stock_moves(moves) super().cancel(shipments) started_shipments = [ shipment for shipment in shipments if shipment.state == 'started'] if started_shipments: cls.write(started_shipments, { 'start_date': None, 'started_by': None, }) def get_vessel_type(self,name=None): if self.vessel: return self.vessel.vessel_type @fields.depends('from_location', 'to_location') def get_shipment_type(self, name=None): if (self.from_location and self.to_location and self.from_location.type == 'supplier' and self.to_location.type == 'customer'): return 'dropship' return 'inbound' def _get_report_primary_move(self): moves = list(self.incoming_moves or self.moves or []) return moves[0] if moves else None def _get_report_primary_lot(self): move = self._get_report_primary_move() return getattr(move, 'lot', None) if move else None def _get_report_trade_line(self): lot = self._get_report_primary_lot() if not lot: return None return getattr(lot, 'sale_line', None) or getattr(lot, 'line', None) def _get_report_insurance_fee(self): for fee in self.fees or []: product = getattr(fee, 'product', None) name = ((getattr(product, 'name', '') or '')).strip().lower() if 'insurance' in name: return fee return None def _get_report_incoming_amount_data(self): total = Decimal('0.0') currency = None for move in (self.incoming_moves or []): move_amount, move_currency = self._get_report_incoming_move_amount( move) total += move_amount if not currency and move_currency: currency = move_currency return total, currency def _get_report_incoming_move_amount(self, move): quantity = Decimal(str(getattr(move, 'quantity', 0) or 0)) unit_price = getattr(move, 'unit_price', None) if unit_price not in (None, ''): move_currency = getattr(move, 'currency', None) return quantity * Decimal(str(unit_price or 0)), move_currency lot = getattr(move, 'lot', None) line = getattr(lot, 'line', None) if lot else None if not lot or not line: return Decimal('0.0'), None lot_quantity = Decimal(str( lot.get_current_quantity_converted() or 0)) line_unit_price = Decimal(str(getattr(line, 'unit_price', 0) or 0)) trade = getattr(line, 'purchase', None) line_currency = getattr(trade, 'currency', None) if trade else None return lot_quantity * line_unit_price, line_currency @staticmethod def _get_report_currency_text(currency): return ( getattr(currency, 'rec_name', None) or getattr(currency, 'code', None) or getattr(currency, 'symbol', None) or '') @staticmethod def _format_report_amount(value): if value in (None, ''): return '' value = Decimal(str(value or 0)).quantize(Decimal('0.01')) return format(value, 'f') @staticmethod def _format_report_quantity(value, digits='0.001'): if value in (None, ''): return '' quantity = Decimal(str(value or 0)).quantize(Decimal(digits)) text = format(quantity, 'f') return text.rstrip('0').rstrip('.') or '0' def _get_report_trade(self): line = self._get_report_trade_line() if not line: return None return getattr(line, 'sale', None) or getattr(line, 'purchase', None) @staticmethod def _get_report_sale_from_lot(lot): sale_line = getattr(lot, 'sale_line', None) if lot else None sale = getattr(sale_line, 'sale', None) if sale_line else None return sale def _get_report_sales(self): sales = [] seen = set() def add_sale(sale): if not sale: return sale_id = getattr(sale, 'id', None) key = sale_id if sale_id is not None else id(sale) if key in seen: return seen.add(key) sales.append(sale) for move in (self.incoming_moves or self.moves or []): add_sale(self._get_report_sale_from_lot( getattr(move, 'lot', None))) for lot_qt in getattr(self, 'lotqt', []) or []: add_sale(self._get_report_sale_from_lot( getattr(lot_qt, 'lot_s', None))) return sales def _get_report_physical_lots(self): lots = [] seen = set() for move in (self.incoming_moves or []): lot = getattr(move, 'lot', None) if not lot or getattr(lot, 'lot_type', None) != 'physic': continue lot_id = getattr(lot, 'id', None) key = lot_id if lot_id is not None else id(lot) if key in seen: continue seen.add(key) lots.append(lot) return lots def _get_report_invoice_lines_from_physical_lots(self): lines = [] seen = set() for lot in self._get_report_physical_lots(): for name in ( 'sale_invoice_line', 'sale_invoice_line_prov', 'invoice_line', 'invoice_line_prov'): line = getattr(lot, name, None) if not line: continue line_id = getattr(line, 'id', None) key = line_id if line_id is not None else id(line) if key in seen: continue seen.add(key) lines.append(line) break return lines @staticmethod def _get_report_invoice_line_snapshot_quantity(line): invoice = getattr(line, 'invoice', None) weights = getattr(invoice, '_get_report_invoice_line_weights', None) if callable(weights): quantity, _ = weights(line) unit_getter = getattr(invoice, '_get_report_invoice_line_unit', None) unit = unit_getter(line) if callable(unit_getter) else None return Decimal(str(quantity or 0)), unit snapshots = list(getattr(line, 'lot_weight_snapshots', []) or []) if snapshots: quantity = sum( Decimal(str(getattr(snapshot, 'net_quantity', 0) or 0)) for snapshot in snapshots) unit = getattr(snapshots[0], 'unit', None) return quantity, unit report_net = getattr(line, 'report_net', '') try: quantity = Decimal(str(report_net or 0)) except Exception: quantity = Decimal(0) if not quantity: quantity = Decimal(str(getattr(line, 'quantity', 0) or 0)) return quantity, getattr(line, 'unit', None) @staticmethod def _get_report_unit_text(unit): return ( getattr(unit, 'symbol', None) or getattr(unit, 'rec_name', None) or getattr(unit, 'name', None) or '') def _get_report_insurance_invoice_quantity(self): total = Decimal(0) unit = None matched = False for line in self._get_report_invoice_lines_from_physical_lots(): quantity, line_unit = self._get_report_invoice_line_snapshot_quantity( line) total += quantity if not unit and line_unit: unit = line_unit matched = True if not matched: return None, None return total, unit def _get_report_weight_totals(self): net = Decimal('0') gross = Decimal('0') for move in (self.incoming_moves or self.moves or []): lot = getattr(move, 'lot', None) if lot: lot_net = ( lot.get_current_quantity() if hasattr(lot, 'get_current_quantity') else lot.get_current_quantity_converted() if hasattr(lot, 'get_current_quantity_converted') else getattr(move, 'quantity', 0) ) lot_gross = ( lot.get_current_gross_quantity() if hasattr(lot, 'get_current_gross_quantity') else lot_net ) net += Decimal(str(lot_net or 0)) gross += Decimal(str(lot_gross or 0)) else: quantity = Decimal(str(getattr(move, 'quantity', 0) or 0)) net += quantity gross += quantity return net, gross @property def report_product_name(self): line = self._get_report_trade_line() product = getattr(line, 'product', None) if line else None if product: return product.name or '' move = self._get_report_primary_move() product = getattr(move, 'product', None) if move else None return getattr(product, 'name', '') or '' @property def report_product_description(self): line = self._get_report_trade_line() product = getattr(line, 'product', None) if line else None if product: return product.description or '' move = self._get_report_primary_move() product = getattr(move, 'product', None) if move else None return getattr(product, 'description', '') or '' @property def report_insurance_footer_ref(self): return self.bl_number or self.number or '' @property def report_insurance_certificate_number(self): return self.bl_number or self.number or '' @property def report_insurance_order_reference(self): sales = self._get_report_sales() if len(sales) != 1: return '' sale = sales[0] return ( getattr(sale, 'report_melya_proforma_number', None) or getattr(sale, 'report_deal', None) or '') @property def report_insurance_account_of(self): company = getattr(self, 'company', None) party = getattr(company, 'party', None) if company else None if not party: return '' address_get = getattr(party, 'address_get', None) address = address_get() if callable(address_get) else None return ( getattr(address, 'party_name', None) or getattr(address, 'party_full_name', None) or getattr(party, 'rec_name', None) or '') @property def report_insurance_goods_description(self): name = self.report_product_name description = self.report_product_description product = ( ' - '.join(part for part in [name, description] if part) if description and description != name else name or description) quantity, unit = self._get_report_insurance_invoice_quantity() if quantity is None: return product quantity_text = self._format_report_quantity(quantity, '0.0001') unit_text = self._get_report_unit_text(unit) quantity_label = ''.join( part for part in [quantity_text, unit_text] if part) return ' '.join(part for part in [quantity_label, product] if part) @property def report_insurance_invoice_number(self): numbers = [] seen = set() for line in self._get_report_invoice_lines_from_physical_lots(): invoice = getattr(line, 'invoice', None) number = ( getattr(invoice, 'number', None) or getattr(invoice, 'rec_name', None) or '') if not number or number in seen: continue seen.add(number) numbers.append(number) return ' '.join(numbers) @property def report_insurance_loading_port(self): return getattr(self.from_location, 'name', '') or '' @property def report_insurance_discharge_port(self): return getattr(self.to_location, 'name', '') or '' @property def report_insurance_transport(self): if self.vessel and self.vessel.vessel_name: return self.vessel.vessel_name return self.transport_type or '' @property def report_insurance_amount(self): insured_amount, insured_currency = self._get_report_incoming_amount_data() if insured_amount: insured_amount *= Decimal('1.10') currency_text = self._get_report_currency_text(insured_currency) amount_text = self._format_report_amount(insured_amount) return ' '.join(part for part in [currency_text, amount_text] if part) fee = self._get_report_insurance_fee() if not fee: return '' currency = getattr(fee, 'currency', None) currency_text = self._get_report_currency_text(currency) amount = self._format_report_amount(fee.get_amount()) return ' '.join(part for part in [currency_text, amount] if part) @property def report_insurance_incoming_amount(self): amount, currency = self._get_report_incoming_amount_data() currency_text = self._get_report_currency_text(currency) amount_text = self._format_report_amount(amount) return ' '.join(part for part in [currency_text, amount_text] if part) @property def report_insurance_amount_insured(self): amount, currency = self._get_report_incoming_amount_data() insured_amount = amount * Decimal('1.10') currency_text = self._get_report_currency_text(currency) amount_text = self._format_report_amount(insured_amount) return ' '.join(part for part in [currency_text, amount_text] if part) @property def report_insurance_surveyor(self): if self.surveyor: return self.surveyor.rec_name or '' if self.controller: return self.controller.rec_name or '' fee = self._get_report_insurance_fee() supplier = getattr(fee, 'supplier', None) if fee else None return getattr(supplier, 'rec_name', '') or '' @property def report_insurance_contact_surveyor(self): return self.report_insurance_surveyor @property def report_insurance_issue_place_and_date(self): Date = Pool().get('ir.date') address = None if self.company and getattr(self.company, 'party', None): address = self.company.party.address_get() place = ( getattr(address, 'city', None) or getattr(self.company.party, 'rec_name', None) if self.company and getattr(self.company, 'party', None) else '' ) or '' today = Date.today() date_text = today.strftime('%d-%m-%Y') if today else '' return ', '.join(part for part in [place, date_text] if part) @property def report_packing_product_class(self): return self.report_product_name @property def report_packing_contract_number(self): trade = self._get_report_trade() return ( getattr(trade, 'reference', None) or getattr(trade, 'number', None) or self.reference or self.number or '') @property def report_packing_invoice_qty(self): quantity = self.quantity if self.quantity not in (None, '') else 0 return self._format_report_quantity(quantity) @property def report_packing_invoice_qty_unit(self): unit = self.unit return ( getattr(unit, 'symbol', None) or getattr(unit, 'rec_name', None) or '') @property def report_packing_origin(self): trade = self._get_report_trade() return ( getattr(trade, 'product_origin', None) or getattr(self.from_location, 'name', None) or '') @property def report_packing_product(self): return self.report_product_name @property def report_packing_counterparty_name(self): trade = self._get_report_trade() party = getattr(trade, 'party', None) if trade else None if party: return party.rec_name or '' return getattr(self.supplier, 'rec_name', '') or '' @property def report_packing_ship_name(self): if self.vessel and self.vessel.vessel_name: return self.vessel.vessel_name return self.transport_type or '' @property def report_packing_loading_port(self): return getattr(self.from_location, 'name', '') or '' @property def report_packing_destination_port(self): return getattr(self.to_location, 'name', '') or '' @property def report_packing_chunk_number(self): return self.bl_number or self.number or '' @property def report_packing_chunk_date(self): if self.bl_date: return self.bl_date.strftime('%d-%m-%Y') return '' @property def report_packing_today_date(self): Date = Pool().get('ir.date') today = Date.today() if not today: return '' return f"{today.strftime('%B')} {today.day}, {today.year}" @property def report_packing_weight_unit(self): line = self._get_report_trade_line() unit = getattr(line, 'unit', None) if line else None if unit: return ( getattr(unit, 'symbol', None) or getattr(unit, 'rec_name', None) or '') return self.report_packing_invoice_qty_unit or 'KGS' @property def report_packing_gross_weight(self): _, gross = self._get_report_weight_totals() return self._format_report_quantity(gross) @property def report_packing_net_weight(self): net, _ = self._get_report_weight_totals() return self._format_report_quantity(net) @property def report_coo_exporter(self): company = getattr(self, 'company', None) party = getattr(company, 'party', None) if company else None if not party: return '' address = party.address_get() if hasattr(party, 'address_get') else None lines = [getattr(party, 'rec_name', None) or getattr(party, 'name', None) or ''] if address and getattr(address, 'full_address', None): lines.append(address.full_address) return '\n'.join(filter(None, lines)) @property def report_coo_consignee(self): trade = self._get_report_trade() party = getattr(trade, 'party', None) if trade else None if not party: return '' address = party.address_get() if hasattr(party, 'address_get') else None lines = [getattr(party, 'rec_name', None) or getattr(party, 'name', None) or ''] if address and getattr(address, 'full_address', None): lines.append(address.full_address) return '\n'.join(filter(None, lines)) @property def report_coo_number(self): return getattr(self, 'reference', None) or self.number or '' @property def report_coo_transport(self): parts = [] if self.bl_number: parts.append(f"B/L {self.bl_number}") ship_name = self.report_packing_ship_name if ship_name: parts.append(ship_name) if self.booking: parts.append(f"Booking {self.booking}") return ' - '.join(parts) @property def report_coo_origin_country(self): return self.report_packing_origin or '' @property def report_coo_observations(self): parts = [] contract = self.report_packing_contract_number if contract: parts.append(f"Contract: {contract}") if self.note: parts.append(self.note) return '\n'.join(filter(None, parts)) @property def report_coo_goods_description(self): parts = [self.report_product_name, self.report_product_description] if self.container: container_numbers = ', '.join( filter(None, (getattr(c, 'container_no', None) or '' for c in self.container))) if container_numbers: parts.append(f"Container(s): {container_numbers}") return '\n'.join(filter(None, parts)) @property def report_coo_net_weight(self): return self.report_packing_net_weight @property def report_coo_gross_weight(self): return self.report_packing_gross_weight @property def report_coo_weight_unit(self): return self.report_packing_weight_unit or '' @property def report_coo_net_weight_display(self): parts = [self.report_coo_net_weight, self.report_coo_weight_unit] return ' '.join(part for part in parts if part) @property def report_coo_gross_weight_display(self): parts = [self.report_coo_gross_weight, self.report_coo_weight_unit] return ' '.join(part for part in parts if part) @property def report_coo_issue_date(self): Date = Pool().get('ir.date') today = Date.today() if not today: return '' return today.strftime('%d-%m-%Y') @staticmethod def _report_record_key(record): record_id = getattr(record, 'id', None) return record_id if record_id is not None else id(record) @classmethod def _report_unique_records(cls, records): unique = [] seen = set() for record in records: if not record: continue key = cls._report_record_key(record) if key in seen: continue seen.add(key) unique.append(record) return unique def _get_report_linkage_lots(self): return self._report_unique_records( getattr(move, 'lot', None) for move in (self.incoming_moves or self.moves or [])) @staticmethod def _report_linkage_lot_purchase_line(lot): line = getattr(lot, 'line', None) if line: return line get_virtual_lot = getattr(lot, 'getVlot_p', None) if callable(get_virtual_lot): try: virtual_lot = get_virtual_lot() except (IndexError, AttributeError): virtual_lot = None return getattr(virtual_lot, 'line', None) return None @staticmethod def _report_linkage_lot_sale_line(lot): line = getattr(lot, 'sale_line', None) if line: return line get_virtual_lot = getattr(lot, 'getVlot_s', None) if callable(get_virtual_lot): try: virtual_lot = get_virtual_lot() except (IndexError, AttributeError): virtual_lot = None return getattr(virtual_lot, 'sale_line', None) return None def _get_report_linkage_purchase_lines(self): return self._report_unique_records( self._report_linkage_lot_purchase_line(lot) for lot in self._get_report_linkage_lots()) def _get_report_linkage_sale_lines(self): return self._report_unique_records( self._report_linkage_lot_sale_line(lot) for lot in self._get_report_linkage_lots()) def _get_report_linkage_purchase(self): purchases = self._report_unique_records( getattr(line, 'purchase', None) for line in self._get_report_linkage_purchase_lines()) return purchases[0] if purchases else None def _get_report_linkage_sale(self): sales = self._report_unique_records( getattr(line, 'sale', None) for line in self._get_report_linkage_sale_lines()) return sales[0] if sales else None @staticmethod def _report_rec_name(record): return ( getattr(record, 'rec_name', None) or getattr(record, 'name', None) or getattr(record, 'number', None) or '') @classmethod def _report_number(cls, record): return ( getattr(record, 'number', None) or getattr(record, 'full_number', None) or cls._report_rec_name(record) or '') @staticmethod def _report_date(value): if not value: return '' if hasattr(value, 'strftime'): return value.strftime('%d %b %y') return str(value) @staticmethod def _report_amount(value): if value in (None, ''): return '' return format(Decimal(str(value or 0)).quantize(Decimal('0.001')), ',f') @staticmethod def _report_price(value): if value in (None, ''): return '' return format(Decimal(str(value or 0)).quantize(Decimal('0.001')), 'f') @classmethod def _report_currency_code(cls, record): currency = getattr(record, 'currency', None) for name in ('code', 'rec_name', 'name', 'symbol'): value = getattr(currency, name, None) if value and not str(value).isdigit(): return value return '' @classmethod def _report_unit_symbol(cls, line): unit = getattr(line, 'unit', None) return ( getattr(unit, 'symbol', None) or getattr(unit, 'rec_name', None) or getattr(unit, 'name', None) or '') @classmethod def _report_line_quantity(cls, line): return Decimal(str( getattr(line, 'quantity_theorical', None) or getattr(line, 'quantity', 0) or 0)) @classmethod def _report_line_amount(cls, line): quantity = cls._report_line_quantity(line) price = Decimal(str(getattr(line, 'unit_price', 0) or 0)) return quantity * price def _get_report_linkage_product(self): lines = ( self._get_report_linkage_purchase_lines() or self._get_report_linkage_sale_lines()) product = getattr(lines[0], 'product', None) if lines else None if product: return product move = self._get_report_primary_move() return getattr(move, 'product', None) if move else None def _get_report_linkage_fees(self): fees = [] seen = set() def add_fee(fee): if not fee: return key = self._report_record_key(fee) if key in seen: return seen.add(key) fees.append(fee) for fee in getattr(self, 'fees', []) or []: add_fee(fee) for line in ( self._get_report_linkage_purchase_lines() + self._get_report_linkage_sale_lines()): for fee in getattr(line, 'fees', []) or []: add_fee(fee) return [ fee for fee in fees if self._report_linkage_fee_applies_to_report_lots(fee)] def _get_report_linkage_fee_lots(self, fee): get_lots = getattr(fee, '_get_effective_fee_lots', None) if callable(get_lots): lots = get_lots() if lots: return list(lots) lots = getattr(fee, 'lots', None) if lots: return list(lots) return [] def _report_linkage_fee_applies_to_report_lots(self, fee): report_lots = self._get_report_linkage_lots() if not report_lots: return True fee_lots = self._get_report_linkage_fee_lots(fee) if fee_lots: report_lot_keys = { self._report_record_key(lot) for lot in report_lots} return any( self._report_record_key(lot) in report_lot_keys for lot in fee_lots) trade_line = self._get_report_linkage_fee_trade_line(fee) report_lines = ( self._get_report_linkage_purchase_lines() + self._get_report_linkage_sale_lines()) report_line_keys = { self._report_record_key(line) for line in report_lines} return ( not trade_line or self._report_record_key(trade_line) in report_line_keys) def _get_report_linkage_fee_trade_line(self, fee): return ( getattr(fee, 'line', None) or getattr(fee, 'sale_line', None) or (self._get_report_linkage_purchase_lines() or self._get_report_linkage_sale_lines() or [None])[0]) def _get_report_linkage_fee_trade(self, fee): trade_line = self._get_report_linkage_fee_trade_line(fee) return ( getattr(trade_line, 'purchase', None) or getattr(trade_line, 'sale', None) or getattr(fee, 'purchase', None) or getattr(fee, 'sale', None)) @classmethod def _report_linkage_fee_type(cls, fee): return getattr(fee, 'type', None) or 'budgeted' def _get_report_linkage_fee_pairs(self): groups = {} order = [] fallback_trade = ( self._get_report_linkage_purchase() or self._get_report_linkage_sale()) for fee in self._get_report_linkage_fees(): trade_line = self._get_report_linkage_fee_trade_line(fee) trade = self._get_report_linkage_fee_trade(fee) or fallback_trade key = ( self._report_record_key(trade_line or trade), self._report_record_key(getattr(fee, 'product', None)), self._report_record_key(getattr(fee, 'supplier', None)), ) if key not in groups: groups[key] = { 'trade': trade, 'budgeted': [], 'ordered': [], } order.append(key) fee_type = self._report_linkage_fee_type(fee) if fee_type in ('budgeted', 'ordered'): groups[key][fee_type].append(fee) pairs = [] for key in order: group = groups[key] budgeted = group['budgeted'] if not budgeted: continue ordered = group['ordered'] estimated = sum( (self._report_fee_amount(fee) for fee in budgeted), Decimal('0')) validated = estimated if ordered: validated = sum( (self._report_fee_amount(fee) for fee in ordered), Decimal('0')) pairs.append({ 'fee': budgeted[0], 'trade': group['trade'], 'estimated': estimated, 'validated': validated, 'ordered': ordered, }) return pairs @classmethod def _report_fee_amount(cls, fee): get_amount = getattr(fee, 'get_amount', None) if callable(get_amount): amount = get_amount() else: amount = None if amount in (None, ''): price = Decimal(str(getattr(fee, 'price', 0) or 0)) quantity = Decimal(str(getattr(fee, 'quantity', 0) or 0)) mode = getattr(fee, 'mode', None) amount = price if mode == 'lumpsum' else price * quantity amount = Decimal(str(amount or 0)) return -amount if getattr(fee, 'p_r', None) == 'pay' else amount @classmethod def _report_linkage_fee_label(cls, fee): product = getattr(fee, 'product', None) label = ( getattr(product, 'name', None) or cls._report_rec_name(product) or 'Fee') if label.startswith('[') and '] ' in label: label = label.split('] ', 1)[1] return label def _get_report_linkage_summary_rows(self): purchase_lines = self._get_report_linkage_purchase_lines() sale_lines = self._get_report_linkage_sale_lines() purchase_total = -sum( self._report_line_amount(line) for line in purchase_lines) sale_total = sum(self._report_line_amount(line) for line in sale_lines) rows = [] if purchase_lines: rows.append(( 'Purchases', '', purchase_total, purchase_total, 'major')) rows.append(('', '', '', '', 'spacer')) if sale_lines: rows.append(('Sales', '', sale_total, sale_total, 'major')) rows.append(('', '', '', '', 'spacer')) fee_estimated_total = Decimal('0') fee_validated_total = Decimal('0') fee_rows = {} for pair in self._get_report_linkage_fee_pairs(): fee = pair['fee'] label = self._report_linkage_fee_label(fee) estimated = pair['estimated'] validated = pair['validated'] fee_estimated_total += estimated fee_validated_total += validated current = fee_rows.get(label, (Decimal('0'), Decimal('0'))) fee_rows[label] = ( current[0] + estimated, current[1] + validated, ) for label, amounts in fee_rows.items(): rows.append(( 'Costs', label, amounts[0], amounts[1], 'cost')) if rows: rows.append(( 'Costs', 'Total', fee_estimated_total, fee_validated_total, 'cost')) rows.append(('', '', '', '', 'spacer')) estimated_pnl = purchase_total + sale_total + fee_estimated_total validated_pnl = purchase_total + sale_total + fee_validated_total rows.append(('P&L', '', estimated_pnl, validated_pnl, 'pnl')) return rows def _get_report_linkage_movement_rows(self): rows = [] purchase_lines = self._get_report_linkage_purchase_lines() sale_lines = self._get_report_linkage_sale_lines() if purchase_lines: rows.append(('Purchase', '', '', '', '', '', '', '', '', '')) for line in purchase_lines: purchase = getattr(line, 'purchase', None) rows.append(self._get_report_linkage_movement_row( 'Deal', purchase, line)) if sale_lines: rows.append(('Sale', '', '', '', '', '', '', '', '', '')) for line in sale_lines: sale = getattr(line, 'sale', None) rows.append(self._get_report_linkage_movement_row( 'Deal', sale, line)) if purchase_lines or sale_lines: balance = ( sum(self._report_line_quantity(line) for line in purchase_lines) - sum(self._report_line_quantity(line) for line in sale_lines)) unit = self._report_unit_symbol( purchase_lines[0] if purchase_lines else sale_lines[0]) rows.append(( 'Balance (Gain/Loss)', '', '', '', ' '.join(part for part in [ self._format_report_quantity(balance), unit.upper() if unit else ''] if part), '', '', '', '', '')) return rows def _get_report_linkage_movement_row(self, kind, trade, line): product = getattr(line, 'product', None) quantity = ' '.join(part for part in [ self._format_report_quantity(self._report_line_quantity(line)), (self._report_unit_symbol(line) or '').upper()] if part) incoterm = getattr(trade, 'incoterm', None) period = getattr(line, 'period_at', None) or '' if period == 'laycan': period = 'Laycan' return ( kind, self._report_number(trade), self._report_rec_name(getattr(trade, 'party', None)), self._report_rec_name(product), quantity, getattr(incoterm, 'code', None) or '', self._report_rec_name(getattr(trade, 'to_location', None)), period, self._report_date(getattr(line, 'from_del', None)), self._report_date(getattr(line, 'to_del', None)), ) def _get_report_linkage_pricing_rows(self): rows = [] for label, lines in ( ('Buy', self._get_report_linkage_purchase_lines()), ('Sell', self._get_report_linkage_sale_lines())): for line in lines: trade = ( getattr(line, 'purchase', None) or getattr(line, 'sale', None)) currency = self._report_currency_code(trade) unit = self._report_unit_symbol(line).upper() price = self._report_price(getattr(line, 'unit_price', 0)) rows.append(( self._report_number(trade), label, (getattr(line, 'price_type', None) or 'Fixed').title(), '', self._report_rec_name(getattr(line, 'del_period', None)), ' '.join(part for part in [ price, '/'.join(part for part in [currency, unit] if part)] if part), )) return rows def _get_report_linkage_detail_rows(self): rows = [] for line in self._get_report_linkage_sale_lines(): sale = getattr(line, 'sale', None) rows.append(self._get_report_linkage_detail_row( 'A', 'Deal revenue', sale, line, self._report_line_amount(line))) for line in self._get_report_linkage_purchase_lines(): purchase = getattr(line, 'purchase', None) rows.append(self._get_report_linkage_detail_row( 'A', 'Deal expense', purchase, line, -self._report_line_amount(line))) for pair in self._get_report_linkage_fee_pairs(): rows.append(self._get_report_linkage_fee_detail_row(pair)) estimated_total = sum((row[6] for row in rows), Decimal('0')) validated_total = sum((row[7] for row in rows), Decimal('0')) if rows: rows.append(( 'A', 'Total', '', '', '', '', estimated_total, validated_total, 'total')) rows.append(( 'P&L', '', '', '', '', '', estimated_total, validated_total, 'pnl')) return rows def _get_report_linkage_detail_row(self, group, label, trade, line, amount): currency = self._report_currency_code(trade) unit = self._report_unit_symbol(line).upper() unit_price = self._report_price(getattr(line, 'unit_price', 0)) quantity = self._format_report_quantity(self._report_line_quantity(line)) return ( group, label, self._report_rec_name(getattr(trade, 'party', None)), self._report_number(trade), ' '.join(part for part in [ unit_price, '/'.join(part for part in [currency, unit] if part)] if part), quantity, amount, amount, 'line', ) def _get_report_linkage_fee_detail_row(self, pair): fee = pair['fee'] product = getattr(fee, 'product', None) trade_line = self._get_report_linkage_fee_trade_line(fee) trade = pair.get('trade') or self._get_report_linkage_fee_trade(fee) currency = self._report_currency_code(fee) or self._report_currency_code(trade) unit = self._get_report_unit_text(getattr(fee, 'unit', None)) quantity = getattr(fee, 'quantity', None) if quantity in (None, '') and trade_line: quantity = self._report_line_quantity(trade_line) return ( 'A', self._report_linkage_fee_label(fee), self._report_rec_name(getattr(fee, 'supplier', None)), self._report_number(trade), ' '.join(part for part in [ self._report_price(getattr(fee, 'price', 0)), '/'.join(part for part in [currency, unit.upper() if unit else ''] if part)] if part), self._format_report_quantity(quantity or 0), pair['estimated'], pair['validated'], 'fee', ) @classmethod def _report_column(cls, rows, index, amount=False): values = [] for row in rows: value = row[index] if amount: value = cls._report_amount(value) values.append(str(value or '')) return '\n'.join(values) @classmethod def _report_table_rows(cls, rows, names, amount_names=()): result = [] for row in rows: values = {} for index, name in enumerate(names): value = row[index] if name in amount_names: value = cls._report_amount(value) values[name] = str(value or '') result.append(values) return result @property def report_linkage_title(self): purchase = self._get_report_linkage_purchase() sale = self._get_report_linkage_sale() reference = ( getattr(purchase, 'reference', None) or getattr(sale, 'reference', None) or getattr(self, 'reference', None) or '') numbers = '/'.join(filter(None, [ self._report_number(purchase), self._report_number(sale), ])) return ' '.join(part for part in ['Linkage', numbers, reference] if part) @property def report_linkage_desk(self): return 'Sulfuric Acid' @property def report_linkage_book(self): purchase_lines = self._get_report_linkage_purchase_lines() product = ( getattr(purchase_lines[0], 'product', None) if purchase_lines else self._get_report_linkage_product()) code = getattr(product, 'code', None) if product else '' period = self.report_linkage_strategy return ' '.join(part for part in [code, period] if part) @property def report_linkage_strategy(self): for line in ( self._get_report_linkage_purchase_lines() or self._get_report_linkage_sale_lines()): strategies = list(getattr(line, 'mtm', []) or []) if strategies: strategy = getattr(strategies[0], 'strategy', None) or strategies[0] return self._report_rec_name(strategy) period = getattr(line, 'del_period', None) if period: return self._report_rec_name(period) return '' @property def report_linkage_bl_date(self): return self.bl_date and self.bl_date.strftime('%A, %B %d, %Y') or '' @property def report_linkage_trading_unit(self): purchase = self._get_report_linkage_purchase() company = getattr(purchase, 'company', None) if purchase else None if not company: company = getattr(self, 'company', None) party = getattr(company, 'party', None) if company else None if party: return self._report_rec_name(party) sale = self._get_report_linkage_sale() party = getattr(sale, 'party', None) if sale else None return self._report_rec_name(party) @property def report_linkage_finance_user(self): trade = self._get_report_linkage_purchase() or self._get_report_linkage_sale() operator = getattr(trade, 'operator', None) if trade else None return self._report_rec_name(operator) @property def report_linkage_summary_groups(self): return self._report_column(self._get_report_linkage_summary_rows(), 0) @property def report_linkage_summary_cost_types(self): return self._report_column(self._get_report_linkage_summary_rows(), 1) @property def report_linkage_summary_estimated(self): return self._report_column( self._get_report_linkage_summary_rows(), 2, amount=True) @property def report_linkage_summary_validated(self): return self._report_column( self._get_report_linkage_summary_rows(), 3, amount=True) @property def report_linkage_summary_rows(self): return self._report_table_rows( self._get_report_linkage_summary_rows(), ('group', 'cost_type', 'estimated', 'validated', 'kind'), {'estimated', 'validated'}) @property def report_linkage_movement_types(self): return self._report_column(self._get_report_linkage_movement_rows(), 0) @property def report_linkage_movement_references(self): return self._report_column(self._get_report_linkage_movement_rows(), 1) @property def report_linkage_movement_counterparts(self): return self._report_column(self._get_report_linkage_movement_rows(), 2) @property def report_linkage_movement_commodities(self): return self._report_column(self._get_report_linkage_movement_rows(), 3) @property def report_linkage_movement_quantities(self): return self._report_column(self._get_report_linkage_movement_rows(), 4) @property def report_linkage_movement_deliveries(self): return self._report_column(self._get_report_linkage_movement_rows(), 5) @property def report_linkage_movement_basis(self): return self._report_column(self._get_report_linkage_movement_rows(), 6) @property def report_linkage_movement_periods(self): return self._report_column(self._get_report_linkage_movement_rows(), 7) @property def report_linkage_movement_from_dates(self): return self._report_column(self._get_report_linkage_movement_rows(), 8) @property def report_linkage_movement_to_dates(self): return self._report_column(self._get_report_linkage_movement_rows(), 9) @property def report_linkage_movement_rows(self): return self._report_table_rows( self._get_report_linkage_movement_rows(), ('type', 'reference', 'counterpart', 'commodity', 'quantity', 'delivery', 'basis', 'period', 'from_date', 'to_date')) @property def report_linkage_bank_deal(self): purchase = self._get_report_linkage_purchase() return self._report_number(purchase) @property def report_linkage_bank_type(self): return 'Buy' if self._get_report_linkage_purchase() else '' @property def report_linkage_bank_name(self): sale = self._get_report_linkage_sale() bank = getattr(sale, 'our_bank_account', None) if sale else None bank_party = getattr(bank, 'bank', None) if bank else None return self._report_rec_name(bank_party) @property def report_linkage_lc_type(self): return 'N/A' @property def report_linkage_lc_number(self): sale = self._get_report_linkage_sale() return getattr(sale, 'lc_number', '') or '' @property def report_linkage_lc_amount(self): return '' @property def report_linkage_lc_expiry_date(self): sale = self._get_report_linkage_sale() return self._report_date(getattr(sale, 'lc_date', None)) @property def report_linkage_pricing_deals(self): return self._report_column(self._get_report_linkage_pricing_rows(), 0) @property def report_linkage_pricing_sides(self): return self._report_column(self._get_report_linkage_pricing_rows(), 1) @property def report_linkage_pricing_types(self): return self._report_column(self._get_report_linkage_pricing_rows(), 2) @property def report_linkage_pricing_symbols(self): return self._report_column(self._get_report_linkage_pricing_rows(), 3) @property def report_linkage_pricing_periods(self): return self._report_column(self._get_report_linkage_pricing_rows(), 4) @property def report_linkage_pricing_input_prices(self): return self._report_column(self._get_report_linkage_pricing_rows(), 5) @property def report_linkage_pricing_rows(self): return self._report_table_rows( self._get_report_linkage_pricing_rows(), ('deal', 'side', 'type', 'symbol', 'period', 'input_price')) @property def report_linkage_detail_groups(self): return self._report_column(self._get_report_linkage_detail_rows(), 0) @property def report_linkage_detail_cost_types(self): return self._report_column(self._get_report_linkage_detail_rows(), 1) @property def report_linkage_detail_counterparts(self): return self._report_column(self._get_report_linkage_detail_rows(), 2) @property def report_linkage_detail_sources(self): return self._report_column(self._get_report_linkage_detail_rows(), 3) @property def report_linkage_detail_unit_prices(self): return self._report_column(self._get_report_linkage_detail_rows(), 4) @property def report_linkage_detail_quantities(self): return self._report_column(self._get_report_linkage_detail_rows(), 5) @property def report_linkage_detail_estimated(self): return self._report_column( self._get_report_linkage_detail_rows(), 6, amount=True) @property def report_linkage_detail_validated(self): return self._report_column( self._get_report_linkage_detail_rows(), 7, amount=True) @property def report_linkage_detail_rows(self): return self._report_table_rows( self._get_report_linkage_detail_rows(), ('group', 'cost_type', 'counterpart', 'source', 'unit_price', 'quantity', 'estimated', 'validated', 'kind'), {'estimated', 'validated'}) def get_rec_name(self, name=None): if self.number: return self.number + '[' + (self.vessel.vessel_name if self.vessel else '') + (('-' + self.travel_nb) if self.travel_nb else '') + ']' else: return str(self.id) def create_fee(self,controller): Fee = Pool().get('fee.fee') Product = Pool().get('product.product') if not controller: logger.info("CREATE_FEE_SKIPPED:NO_CONTROLLER shipment=%s", self) return fee = Fee() fee.shipment_in = self.id fee.supplier = controller fee.type = 'budgeted' fee.p_r = 'pay' price,mode,curr,unit = controller.get_sla_cost(self.to_location) if not (price and mode and curr and unit): logger.info( "CREATE_FEE_SKIPPED:NO_SLA shipment=%s controller=%s location=%s", self, controller, self.to_location) return fee.mode = mode fee.currency = curr fee.unit = unit fee.quantity = self.get_bales() or 1 fee.product = Product.get_by_name('Reweighing') fee.price = price Fee.save([fee]) def get_controller(self): ControllerCategory = Pool().get('party.category') PartyCategory = Pool().get('party.party-party.category') cc = ControllerCategory.search(['name','=','CONTROLLER']) if cc: cc = cc[0] controllers = PartyCategory.search(['category','=',cc.id]) prioritized = [] for c in controllers: if not c.party.IsAvailableForControl(self): continue gap, rule = c.party.get_controller_execution_priority(self) prioritized.append(( 1 if rule else 0, gap if gap is not None else Decimal('-999999'), c.party, )) if prioritized: prioritized.sort(key=lambda item: (item[0], item[1]), reverse=True) return prioritized[0][2] def get_instructions_html(self,inv_date,inv_nb): vessel = self.vessel.vessel_name if self.vessel else "" lines = [ "
Hi,
", "Please find details below for the requested control
", ] lines.append( "" f"BL number: {self.bl_number} | " f"Vessel: {vessel} | " f"ETA: {self.etad}" "
" ) if self.incoming_moves: tot_net = sum([m.lot.get_current_quantity() for m in self.incoming_moves]) tot_gross = sum([m.lot.get_current_gross_quantity() for m in self.incoming_moves]) tot_bale = sum([m.lot.lot_qt for m in self.incoming_moves]) customer = self.incoming_moves[0].lot.sale_line.sale.party.name if self.incoming_moves[0].lot.sale_line else "" unit = self.incoming_moves[0].lot.lot_unit_line.symbol lines.append("" f"Customer: {customer} | " f"Invoice Nb: {inv_nb} | " f"Invoice Date: {inv_date}" "
" ) lines.append( "" f"Nb Bales: {tot_bale} | " f"Net Qt: {tot_net} {unit} | " f"Gross Qt: {tot_gross} {unit}" "
" ) return "".join(lines) # def get_instructions(self): # lines = [ # "Hi,", # "", # "Please find details below for the requested control", # f"BL number: {self.bl_number}", # "" # ] # if self.incoming_moves: # for m in self.incoming_moves: # if m.lot: # lines.append( # f"Lot nb: {m.lot.lot_name} | " # f"Net Qt: {m.lot.get_current_quantity()} {m.lot.lot_unit.symbol} | " # f"Gross Qt: {m.lot.get_current_gross_quantity()} {m.lot.lot_unit.symbol}" # ) # return "\n".join(lines) def _create_lots_from_fintrade(self): t = Table('freight_booking_lots') cursor = Transaction().connection.cursor() query = t.select( t.BOOKING_NUMBER, t.LOT_NUMBER, t.LOT_NBR_BALES, t.LOT_GROSS_WEIGHT, t.LOT_NET_WEIGHT, t.LOT_UOM, t.LOT_QUALITY, t.CUSTOMER, t.SELL_PRICE_CURRENCY, t.SELL_PRICE_UNIT, t.SELL_PRICE, t.SALE_INVOICE, t.SELL_INV_AMOUNT, t.SALE_INVOICE_DATE, t.SELL_PREMIUM, t.SALE_CONTRACT_NUMBER, t.SALE_DECLARATION_KEY, t.SHIPMENT_CHUNK_KEY, where=(t.BOOKING_NUMBER == int(self.reference)) ) cursor.execute(*query) rows = cursor.fetchall() logger.info( "FINTRADE_LOTS_START shipment=%s reference=%s bl=%s rows=%s", getattr(self, 'id', None), self.reference, self.bl_number, len(rows)) logger.info("ROWS:%s",rows) inv_date = None inv_nb = None if rows: sale_line = None for row in rows: logger.info("ROW:%s",row) #Purchase & Sale creation LotQt = Pool().get('lot.qt') Lot = Pool().get('lot.lot') LotAdd = Pool().get('lot.add.line') Currency = Pool().get('currency.currency') Product = Pool().get('product.product') Party = Pool().get('party.party') Uom = Pool().get('product.uom') Sale = Pool().get('sale.sale') SaleLine = Pool().get('sale.line') dec_key = str(row[16]).strip() chunk_key = str(row[17]).strip() chunk_key_int = None if chunk_key and chunk_key.lower() not in {'none', 'null'}: try: chunk_key_int = int(chunk_key) except (TypeError, ValueError): logger.info( "FINTRADE_INVALID_CHUNK_KEY shipment=%s " "booking=%s declaration=%s chunk=%s", getattr(self, 'id', None), self.reference, dec_key, chunk_key) lot_unit = str(row[5]).strip().lower() product = str(row[6]).strip().upper() lot_net_weight = Decimal(row[4]) logger.info("LOT_NET_WEIGHT:%s",lot_net_weight) lot_gross_weight = Decimal(row[3]) lot_bales = Decimal(row[2]) lot_number = row[1] customer = str(row[7]).strip().upper() sell_price_currency = str(row[8]).strip().upper() sell_price_unit = str(row[9]).strip().lower() inv_date = str(row[13]).strip() inv_nb = str(row[11]).strip() sell_price = Decimal(row[10]) premium = Decimal(row[14]) reference = Decimal(row[15]) logger.info( "FINTRADE_LOT_ROW shipment=%s booking=%s lot_number=%s " "chunk=%s declaration=%s net=%s gross=%s bales=%s " "lot_unit=%s sell_currency=%s sell_unit=%s customer=%s " "sale_contract=%s", getattr(self, 'id', None), self.reference, lot_number, chunk_key, dec_key, lot_net_weight, lot_gross_weight, lot_bales, lot_unit, sell_price_currency, sell_price_unit, customer, reference) if chunk_key_int is not None: existing_chunk_lots = Lot.search([ ('lot_chunk_key', '=', chunk_key_int), ]) logger.info( "FINTRADE_CHUNK_CHECK chunk=%s existing_lots=%s", chunk_key, [ { 'id': lot.id, 'name': getattr(lot, 'lot_name', None), 'shipment_in': ( lot.lot_shipment_in.id if getattr(lot, 'lot_shipment_in', None) else None), 'purchase_line': ( lot.line.id if getattr(lot, 'line', None) else None), 'sale_line': ( lot.sale_line.id if getattr(lot, 'sale_line', None) else None), } for lot in existing_chunk_lots ]) logger.info("DECLARATION_KEY:%s",dec_key) declaration = SaleLine.search(['note','=',dec_key]) logger.info( "FINTRADE_DECLARATION_SEARCH declaration=%s count=%s " "sale_line_ids=%s", dec_key, len(declaration), [d.id for d in declaration]) if declaration: sale_line = declaration[0] logger.info( "FINTRADE_DECLARATION_REUSE declaration=%s " "sale_line=%s sale=%s quantity=%s theoretical=%s " "lot_ids=%s", dec_key, sale_line.id, sale_line.sale.id if sale_line.sale else None, sale_line.quantity, sale_line.quantity_theorical, [lot.id for lot in sale_line.lots]) vlot = sale_line.lots[0] lqt = LotQt.search([('lot_s','=',vlot.id)]) logger.info( "FINTRADE_DECLARATION_LQT declaration=%s vlot=%s " "vlot_qty=%s lqt_count=%s lqts=%s", dec_key, vlot.id, vlot.lot_quantity, len(lqt), [ { 'id': q.id, 'lot_p': q.lot_p.id if q.lot_p else None, 'lot_s': q.lot_s.id if q.lot_s else None, 'quantity': q.lot_quantity, 'unit': q.lot_unit.id if q.lot_unit else None, 'shipment_in': ( q.lot_shipment_in.id if q.lot_shipment_in else None), 'shipment_origin': q.lot_shipment_origin, 'status': q.lot_status, 'availability': q.lot_av, } for q in lqt ]) if lqt: for lq in lqt: if lq.lot_p: logger.info("VLOT_P:%s",lq.lot_p) sale_line.quantity_theorical += round(lot_net_weight,2) SaleLine.save([sale_line]) lq.lot_p.updateVirtualPart(round(lot_net_weight,2),self,lq.lot_s) vlot.set_current_quantity(round(lot_net_weight,2),round(lot_gross_weight,2),1) Lot.save([vlot]) else: sale = Sale() sale_line = SaleLine() sale.party = Party.getPartyByName(customer,'CLIENT') logger.info("SALE_PARTY:%s",sale.party) sale.reference = reference sale.from_location = self.from_location sale.to_location = self.to_location sale.company = 6 sale.payment_term = 2 if sale.party.addresses: sale.invoice_address = sale.party.addresses[0] sale.shipment_address = sale.party.addresses[0] if sell_price_currency == 'USC': sale.currency = Currency.get_by_name('USD') sale_line.enable_linked_currency = True sale_line.linked_currency = 1 sale_line.linked_unit = Uom.get_by_name(sell_price_unit) sale_line.linked_price = round(sell_price,4) else: sale.currency = Currency.get_by_name(sell_price_currency) sale_line.unit_price = round(sell_price,4) sale_line.unit = Uom.get_by_name(sell_price_unit) sale_line.premium = premium Sale.save([sale]) sale_line.sale = sale.id sale_line.quantity = round(lot_net_weight,2) sale_line.quantity_theorical = round(lot_net_weight,2) sale_line.product = Product.get_by_name('BRAZIL COTTON') logger.info("PRODUCT:%s",sale_line.product) sale_line.unit = Uom.get_by_name(lot_unit) if sell_price_currency == 'USC': sale_line.unit_price = sale_line.get_price_linked_currency() sale_line.price_type = 'priced' sale_line.created_by_code = False sale_line.note = dec_key SaleLine.save([sale_line]) #need to link the virtual part to the shipment lqt = LotQt.search([('lot_s','=',sale_line.lots[0])]) logger.info( "FINTRADE_NEW_SALE_LQT sale=%s sale_line=%s " "vlot=%s vlot_qty=%s lqt_count=%s lqts=%s", sale.id, sale_line.id, sale_line.lots[0].id, sale_line.lots[0].lot_quantity, len(lqt), [ { 'id': q.id, 'lot_p': q.lot_p.id if q.lot_p else None, 'lot_s': q.lot_s.id if q.lot_s else None, 'quantity': q.lot_quantity, 'unit': q.lot_unit.id if q.lot_unit else None, 'shipment_in': ( q.lot_shipment_in.id if q.lot_shipment_in else None), 'shipment_origin': q.lot_shipment_origin, 'status': q.lot_status, 'availability': q.lot_av, } for q in lqt ]) if lqt: lqt[0].lot_shipment_in = self LotQt.save(lqt) logger.info("SALE_LINKED_TO_SHIPMENT:%s",self) ContractStart = Pool().get('contracts.start') ContractDetail = Pool().get('contract.detail') ct = ContractStart() d = ContractDetail() ct.type = 'Purchase' ct.matched = True ct.shipment_in = self ct.lot = sale_line.lots[0] ct.product = sale_line.product ct.unit = sale_line.unit d.party = Party.getPartyByName('FAIRCOT') if sale_line.enable_linked_currency: d.currency_unit = str(sale_line.linked_currency.id) + '_' + str(sale_line.linked_unit.id) else: d.currency_unit = str(sale.currency.id) + '_' + str(sale_line.unit.id) d.quantity = sale_line.quantity d.unit = sale_line.unit d.price = sale_line.unit_price d.price_type = 'priced' d.crop = None d.tol_min = 0 d.tol_max = 0 d.incoterm = None d.reference = str(sale.id) d.from_location = sale.from_location d.to_location = sale.to_location d.del_period = None d.from_del = None d.to_del = None d.payment_term = sale.payment_term ct.contracts = [d] logger.info( "FINTRADE_CREATE_PURCHASE_FROM_SALE shipment=%s " "declaration=%s chunk=%s sale=%s sale_line=%s " "source_lot=%s source_lot_qty=%s requested=%s " "ct_quantity=%s active_ids=%s", getattr(self, 'id', None), dec_key, chunk_key, sale.id, sale_line.id, ct.lot.id, ct.lot.lot_quantity, d.quantity, getattr(ct, 'quantity', None), Transaction().context.get('active_ids')) ContractFactory.create_contracts( ct.contracts, type_=ct.type, ct=ct, ) #Lots creation vlot = sale_line.lots[0] lqt = LotQt.search([('lot_s','=',vlot.id),('lot_p','>',0)]) logger.info( "FINTRADE_PHYSICAL_LOT_SOURCE shipment=%s declaration=%s " "chunk=%s vlot=%s vlot_qty=%s source_lqt_count=%s " "source_lqts=%s", getattr(self, 'id', None), dec_key, chunk_key, vlot.id, vlot.lot_quantity, len(lqt), [ { 'id': q.id, 'lot_p': q.lot_p.id if q.lot_p else None, 'lot_s': q.lot_s.id if q.lot_s else None, 'quantity': q.lot_quantity, 'unit': q.lot_unit.id if q.lot_unit else None, 'shipment_in': ( q.lot_shipment_in.id if q.lot_shipment_in else None), 'shipment_origin': q.lot_shipment_origin, 'status': q.lot_status, 'availability': q.lot_av, } for q in lqt ]) if lqt and vlot.lot_quantity > 0: lqt = lqt[0] l = LotAdd() l.lot_qt = lot_bales l.lot_unit = Uom.get_by_name('bale') l.lot_unit_line = Uom.get_by_name(lot_unit) l.lot_quantity = round(lot_net_weight,2) l.lot_gross_quantity = round(lot_gross_weight,2) l.lot_premium = premium l.lot_chunk_key = int(chunk_key) logger.info( "FINTRADE_ADD_PHYSICAL_LOT shipment=%s " "declaration=%s chunk=%s source_lqt=%s " "source_open_before=%s add_net=%s add_gross=%s " "add_bales=%s", getattr(self, 'id', None), dec_key, chunk_key, lqt.id, lqt.lot_quantity, l.lot_quantity, l.lot_gross_quantity, l.lot_qt) logger.info("ADD_LOT:%s",int(chunk_key)) LotQt.add_physical_lots(lqt,[l]) logger.info( "FINTRADE_ADD_PHYSICAL_LOT_DONE shipment=%s " "declaration=%s chunk=%s source_lqt=%s", getattr(self, 'id', None), dec_key, chunk_key, lqt.id) else: logger.info( "FINTRADE_SKIP_PHYSICAL_LOT shipment=%s " "declaration=%s chunk=%s reason=%s vlot_qty=%s " "source_lqt_count=%s", getattr(self, 'id', None), dec_key, chunk_key, 'no matched purchase lqt' if not lqt else 'vlot quantity not positive', vlot.lot_quantity, len(lqt)) return inv_date,inv_nb def html_to_text(self,html_content): text = re.sub(r"