You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
22 KiB
503 lines
22 KiB
# -*- coding: utf-8 -*-
|
|
###################################################################################
|
|
#
|
|
# Cybrosys Technologies Pvt. Ltd.
|
|
#
|
|
# Copyright (C) 2022-TODAY Cybrosys Technologies (<https://www.cybrosys.com>).
|
|
# Author: Megha K (<https://www.cybrosys.com>)
|
|
#
|
|
# This program is free software: you can modify
|
|
# it under the terms of the GNU Affero General Public License (AGPL) as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
#
|
|
###################################################################################
|
|
|
|
from werkzeug import urls
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class FreightOrder(models.Model):
|
|
_name = 'freight.order'
|
|
_description = 'Freight Order'
|
|
|
|
name = fields.Char('Name', default='New', readonly=True)
|
|
shipper_id = fields.Many2one('res.partner', 'Shipper', required=True,
|
|
help="Shipper's Details")
|
|
consignee_id = fields.Many2one('res.partner', 'Consignee',
|
|
help="Details of consignee")
|
|
type = fields.Selection([('import', 'Import'), ('export', 'Export')],
|
|
'Import/Export', required=True,
|
|
help="Type of freight operation")
|
|
transport_type = fields.Selection([('land', 'Land'), ('air', 'Air'),
|
|
('water', 'Water')], "Transport",
|
|
help='Type of transportation',
|
|
required=True)
|
|
land_type = fields.Selection([('ltl', 'LTL'), ('ftl', 'FTL')],
|
|
'Land Shipping',
|
|
help="Types of shipment movement involved in Land")
|
|
water_type = fields.Selection([('fcl', 'FCL'), ('lcl', 'LCL')],
|
|
'Water Shipping',
|
|
help="Types of shipment movement involved in Water")
|
|
order_date = fields.Date('Date', default=fields.Date.today(),
|
|
help="Date of order")
|
|
loading_port_id = fields.Many2one('freight.port', string="Loading Port",
|
|
required=True,
|
|
help="Loading port of the freight order")
|
|
discharging_port_id = fields.Many2one('freight.port',
|
|
string="Discharging Port",
|
|
required=True,
|
|
help="Discharging port of freight order")
|
|
state = fields.Selection([('draft', 'Draft'), ('submit', 'Submitted'),
|
|
('confirm', 'Confirmed'),
|
|
('invoice', 'Invoiced'), ('done', 'Done'),
|
|
('cancel', 'Cancel')], default='draft')
|
|
clearance = fields.Boolean("Clearance")
|
|
clearance_count = fields.Integer(compute='compute_count')
|
|
invoice_count = fields.Integer(compute='compute_count')
|
|
total_order_price = fields.Float('Total',
|
|
compute='_compute_total_order_price')
|
|
total_volume = fields.Float('Total Volume',
|
|
compute='_compute_total_order_price')
|
|
total_weight = fields.Float('Total Weight',
|
|
compute='_compute_total_order_price')
|
|
order_ids = fields.One2many('freight.order.line', 'order_id')
|
|
route_ids = fields.One2many('freight.order.routes.line', 'route_id')
|
|
total_route_sale = fields.Float('Total Sale',
|
|
compute="_compute_total_route_cost")
|
|
service_ids = fields.One2many('freight.order.service', 'line_id')
|
|
total_service_sale = fields.Float('Service Total Sale',
|
|
compute="_compute_total_service_cost")
|
|
agent_id = fields.Many2one('res.partner', 'Agent', required=True,
|
|
help="Details of agent")
|
|
expected_date = fields.Date('Expected Date')
|
|
track_ids = fields.One2many('freight.track', 'track_id')
|
|
|
|
@api.depends('order_ids.total_price', 'order_ids.volume',
|
|
'order_ids.weight')
|
|
def _compute_total_order_price(self):
|
|
"""Computing the price of the order"""
|
|
for rec in self:
|
|
rec.total_order_price = sum(rec.order_ids.mapped('total_price'))
|
|
rec.total_volume = sum(rec.order_ids.mapped('volume'))
|
|
rec.total_weight = sum(rec.order_ids.mapped('weight'))
|
|
|
|
@api.depends('route_ids.sale')
|
|
def _compute_total_route_cost(self):
|
|
"""Computing the total cost of route operation"""
|
|
for rec in self:
|
|
rec.total_route_sale = sum(rec.route_ids.mapped('sale'))
|
|
|
|
@api.depends('service_ids.total_sale')
|
|
def _compute_total_service_cost(self):
|
|
"""Computing the total cost of services"""
|
|
for rec in self:
|
|
rec.total_service_sale = sum(rec.service_ids.mapped('total_sale'))
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
"""Create Sequence"""
|
|
sequence_code = 'freight.order.sequence'
|
|
vals['name'] = self.env['ir.sequence'].next_by_code(sequence_code)
|
|
return super(FreightOrder, self).create(vals)
|
|
|
|
def create_custom_clearance(self):
|
|
"""Create custom clearance"""
|
|
clearance = self.env['custom.clearance'].create({
|
|
'name': 'CC - ' + self.name,
|
|
'freight_id': self.id,
|
|
'date': self.order_date,
|
|
'loading_port_id': self.loading_port_id.id,
|
|
'discharging_port_id': self.discharging_port_id.id,
|
|
'agent_id': self.agent_id.id,
|
|
})
|
|
result = {
|
|
'name': 'action.name',
|
|
'type': 'ir.actions.act_window',
|
|
'views': [[False, 'form']],
|
|
'target': 'current',
|
|
'res_id': clearance.id,
|
|
'res_model': 'custom.clearance',
|
|
}
|
|
self.clearance = True
|
|
return result
|
|
|
|
def get_custom_clearance(self):
|
|
"""Get custom clearance"""
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Custom Clearance',
|
|
'view_mode': 'tree,form',
|
|
'res_model': 'custom.clearance',
|
|
'domain': [('freight_id', '=', self.id)],
|
|
'context': "{'create': False}"
|
|
}
|
|
|
|
def track_order(self):
|
|
"""Track the order"""
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Received/Delivered',
|
|
'view_mode': 'form',
|
|
'target': 'new',
|
|
'res_model': 'freight.order.track',
|
|
'context': {
|
|
'default_freight_id': self.id
|
|
}
|
|
}
|
|
|
|
def create_invoice(self):
|
|
"""Create invoice"""
|
|
lines = []
|
|
if self.order_ids:
|
|
for order in self.order_ids:
|
|
value = (0, 0, {
|
|
'name': order.product_id.name,
|
|
'price_unit': order.price,
|
|
'quantity': order.volume + order.weight,
|
|
})
|
|
lines.append(value)
|
|
|
|
if self.route_ids:
|
|
for route in self.route_ids:
|
|
value = (0, 0, {
|
|
'name': route.operation_id.name,
|
|
'price_unit': route.sale,
|
|
})
|
|
lines.append(value)
|
|
|
|
if self.service_ids:
|
|
for service in self.service_ids:
|
|
value = (0, 0, {
|
|
'name': service.service_id.name,
|
|
'price_unit': service.sale,
|
|
'quantity': service.qty
|
|
})
|
|
lines.append(value)
|
|
|
|
invoice_line = {
|
|
'move_type': 'out_invoice',
|
|
'partner_id': self.shipper_id.id,
|
|
'invoice_user_id': self.env.user.id,
|
|
'invoice_origin': self.name,
|
|
'ref': self.name,
|
|
'invoice_line_ids': lines,
|
|
}
|
|
inv = self.env['account.move'].create(invoice_line)
|
|
result = {
|
|
'name': 'action.name',
|
|
'type': 'ir.actions.act_window',
|
|
'views': [[False, 'form']],
|
|
'target': 'current',
|
|
'res_id': inv.id,
|
|
'res_model': 'account.move',
|
|
}
|
|
self.state = 'invoice'
|
|
return result
|
|
|
|
def action_cancel(self):
|
|
"""Cancel the record"""
|
|
if self.state == 'draft' and self.state == 'submit':
|
|
self.state = 'cancel'
|
|
else:
|
|
raise ValidationError("You can't cancel this order")
|
|
|
|
def get_invoice(self):
|
|
"""View the invoice"""
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Invoice',
|
|
'view_mode': 'tree,form',
|
|
'res_model': 'account.move',
|
|
'domain': [('ref', '=', self.name)],
|
|
'context': "{'create': False}"
|
|
}
|
|
|
|
@api.depends('name')
|
|
def compute_count(self):
|
|
"""Compute custom clearance and account move's count"""
|
|
for rec in self:
|
|
if rec.env['custom.clearance'].search([('freight_id', '=', rec.id)]):
|
|
rec.clearance_count = rec.env['custom.clearance'].search_count(
|
|
[('freight_id', '=', rec.id)])
|
|
else:
|
|
rec.clearance_count = 0
|
|
if rec.env['account.move'].search([('ref', '=', rec.name)]):
|
|
rec.invoice_count = rec.env['account.move'].search_count(
|
|
[('ref', '=', rec.name)])
|
|
else:
|
|
rec.invoice_count = 0
|
|
|
|
def action_submit(self):
|
|
"""Submitting order"""
|
|
for rec in self:
|
|
rec.state = 'submit'
|
|
base_url = self.env['ir.config_parameter'].sudo().get_param(
|
|
'web.base.url')
|
|
Urls = urls.url_join(base_url, 'web#id=%(id)s&model=freight.order&view_type=form' % {'id': self.id})
|
|
|
|
mail_content = _('Hi %s,<br>'
|
|
'The Freight Order %s is Submitted'
|
|
'<div style = "text-align: center; '
|
|
'margin-top: 16px;"><a href = "%s"'
|
|
'style = "padding: 5px 10px; font-size: 12px; '
|
|
'line-height: 18px; color: #FFFFFF; '
|
|
'border-color:#875A7B;text-decoration: none; '
|
|
'display: inline-block; margin-bottom: 0px; '
|
|
'font-weight: 400;text-align: center; '
|
|
'vertical-align: middle; cursor: pointer; '
|
|
'white-space: nowrap; background-image: none; '
|
|
'background-color: #875A7B; '
|
|
'border: 1px solid #875A7B; border-radius:3px;">'
|
|
'View %s</a></div>'
|
|
) % (rec.agent_id.name, rec.name, Urls, rec.name)
|
|
email_to = self.env['res.partner'].search([
|
|
('id', 'in', (self.shipper_id.id, self.consignee_id.id,
|
|
self.agent_id.id))])
|
|
for mail in email_to:
|
|
main_content = {
|
|
'subject': _('Freight Order %s is Submitted') % self.name,
|
|
'author_id': self.env.user.partner_id.id,
|
|
'body_html': mail_content,
|
|
'email_to': mail.email
|
|
}
|
|
mail_id = self.env['mail.mail'].create(main_content)
|
|
mail_id.mail_message_id.body = mail_content
|
|
mail_id.send()
|
|
|
|
def action_confirm(self):
|
|
"""Confirm order"""
|
|
for rec in self:
|
|
clearance = self.env['custom.clearance'].search([
|
|
('freight_id', '=', self.id)])
|
|
if clearance:
|
|
if clearance.state == 'confirm':
|
|
rec.state = 'confirm'
|
|
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
|
Urls = urls.url_join(base_url, 'web#id=%(id)s&model=freight.order&view_type=form' % {'id': self.id})
|
|
mail_content = _('Hi %s,<br> '
|
|
'The Freight Order %s is Confirmed '
|
|
'<div style = "text-align: center; '
|
|
'margin-top: 16px;"><a href = "%s"'
|
|
'style = "padding: 5px 10px; '
|
|
'font-size: 12px; line-height: 18px; '
|
|
'color: #FFFFFF; border-color:#875A7B; '
|
|
'text-decoration: none; '
|
|
'display: inline-block; '
|
|
'margin-bottom: 0px; font-weight: 400;'
|
|
'text-align: center; '
|
|
'vertical-align: middle; '
|
|
'cursor: pointer; white-space: nowrap; '
|
|
'background-image: none; '
|
|
'background-color: #875A7B; '
|
|
'border: 1px solid #875A7B; '
|
|
'border-radius:3px;">'
|
|
'View %s</a></div>'
|
|
) % (rec.agent_id.name, rec.name,
|
|
Urls, rec.name)
|
|
email_to = self.env['res.partner'].search([
|
|
('id', 'in', (self.shipper_id.id,
|
|
self.consignee_id.id, self.agent_id.id))])
|
|
for mail in email_to:
|
|
main_content = {
|
|
'subject': _('Freight Order %s is Confirmed') % self.name,
|
|
'author_id': self.env.user.partner_id.id,
|
|
'body_html': mail_content,
|
|
'email_to': mail.email
|
|
}
|
|
mail_id = self.env['mail.mail'].create(main_content)
|
|
mail_id.mail_message_id.body = mail_content
|
|
mail_id.send()
|
|
elif clearance.state == 'draft':
|
|
raise ValidationError("the custom clearance ' %s ' is "
|
|
"not confirmed" % clearance.name)
|
|
else:
|
|
raise ValidationError("Create a custom clearance for %s" % rec.name)
|
|
|
|
for line in rec.order_ids:
|
|
line.container_id.state = 'reserve'
|
|
|
|
def action_done(self):
|
|
"""Mark order as done"""
|
|
for rec in self:
|
|
base_url = self.env['ir.config_parameter'].sudo().get_param(
|
|
'web.base.url')
|
|
Urls = urls.url_join(base_url, 'web#id=%(id)s&model=freight.order&view_type=form' % {'id': self.id})
|
|
|
|
mail_content = _('Hi %s,<br>'
|
|
'The Freight Order %s is Completed'
|
|
'<div style = "text-align: center; '
|
|
'margin-top: 16px;"><a href = "%s"'
|
|
'style = "padding: 5px 10px; font-size: 12px; '
|
|
'line-height: 18px; color: #FFFFFF; '
|
|
'border-color:#875A7B;text-decoration: none; '
|
|
'display: inline-block; '
|
|
'margin-bottom: 0px; font-weight: 400;'
|
|
'text-align: center; vertical-align: middle; '
|
|
'cursor: pointer; white-space: nowrap; '
|
|
'background-image: none; '
|
|
'background-color: #875A7B; '
|
|
'border: 1px solid #875A7B; border-radius:3px;">'
|
|
'View %s</a></div>'
|
|
) % (rec.agent_id.name, rec.name, Urls, rec.name)
|
|
email_to = self.env['res.partner'].search([
|
|
('id', 'in', (self.shipper_id.id, self.consignee_id.id,
|
|
self.agent_id.id))])
|
|
for mail in email_to:
|
|
main_content = {
|
|
'subject': _('Freight Order %s is completed') % self.name,
|
|
'author_id': self.env.user.partner_id.id,
|
|
'body_html': mail_content,
|
|
'email_to': mail.email
|
|
}
|
|
mail_id = self.env['mail.mail'].create(main_content)
|
|
mail_id.mail_message_id.body = mail_content
|
|
mail_id.send()
|
|
self.state = 'done'
|
|
|
|
for line in rec.order_ids:
|
|
line.container_id.state = 'available'
|
|
|
|
|
|
class FreightOrderLine(models.Model):
|
|
_name = 'freight.order.line'
|
|
|
|
order_id = fields.Many2one('freight.order')
|
|
container_id = fields.Many2one('freight.container', string='Container',
|
|
domain="[('state', '=', 'available')]")
|
|
product_id = fields.Many2one('product.product', string='Goods')
|
|
billing_type = fields.Selection([('weight', 'Weight'),
|
|
('volume', 'Volume')], string="Billing On")
|
|
pricing_id = fields.Many2one('freight.price', string='Pricing')
|
|
price = fields.Float('Unit Price')
|
|
total_price = fields.Float('Total Price')
|
|
volume = fields.Float('Volume')
|
|
weight = fields.Float('Weight')
|
|
|
|
@api.constrains('weight')
|
|
def _check_weight(self):
|
|
"""Checking the weight of containers"""
|
|
for rec in self:
|
|
if rec.container_id and rec.billing_type:
|
|
if rec.billing_type == 'weight':
|
|
if rec.container_id.weight < rec.weight:
|
|
raise ValidationError(
|
|
'The weight is must be less '
|
|
'than or equal to %s' % (rec.container_id.weight))
|
|
|
|
@api.constrains('volume')
|
|
def _check_volume(self):
|
|
"""Checking the volume of containers"""
|
|
for rec in self:
|
|
if rec.container_id and rec.billing_type:
|
|
if rec.billing_type == 'volume':
|
|
if rec.container_id.volume < rec.volume:
|
|
raise ValidationError(
|
|
'The volume is must be less '
|
|
'than or equal to %s' % (rec.container_id.volume))
|
|
|
|
@api.onchange('pricing_id', 'billing_type')
|
|
def onchange_price(self):
|
|
"""Calculate the weight and volume of container"""
|
|
for rec in self:
|
|
if rec.billing_type == 'weight':
|
|
rec.volume = 0.00
|
|
rec.price = rec.pricing_id.weight
|
|
elif rec.billing_type == 'volume':
|
|
rec.weight = 0.00
|
|
rec.price = rec.pricing_id.volume
|
|
|
|
@api.onchange('pricing_id', 'billing_type', 'volume', 'weight')
|
|
def onchange_total_price(self):
|
|
"""Calculate sub total price"""
|
|
for rec in self:
|
|
if rec.billing_type and rec.pricing_id:
|
|
if rec.billing_type == 'weight':
|
|
rec.total_price = rec.weight * rec.price
|
|
elif rec.billing_type == 'volume':
|
|
rec.total_price = rec.volume * rec.price
|
|
|
|
|
|
class FreightOrderRouteLine(models.Model):
|
|
_name = 'freight.order.routes.line'
|
|
|
|
route_id = fields.Many2one('freight.order')
|
|
operation_id = fields.Many2one('freight.routes', required=True)
|
|
source_loc = fields.Many2one('freight.port', 'Source Location')
|
|
destination_loc = fields.Many2one('freight.port', 'Destination Location')
|
|
transport_type = fields.Selection([('land', 'Land'), ('air', 'Air'),
|
|
('water', 'Water')], "Transport",
|
|
required=True)
|
|
sale = fields.Float('Sale')
|
|
|
|
@api.onchange('operation_id', 'transport_type')
|
|
def _onchange_operation_id(self):
|
|
"""calculate the price of route operation"""
|
|
for rec in self:
|
|
if rec.operation_id and rec.transport_type:
|
|
if rec.transport_type == 'land':
|
|
rec.sale = rec.operation_id.land_sale
|
|
elif rec.transport_type == 'air':
|
|
rec.sale = rec.operation_id.air_sale
|
|
elif rec.transport_type == 'water':
|
|
rec.sale = rec.operation_id.water_sale
|
|
|
|
|
|
class FreightOrderServiceLine(models.Model):
|
|
_name = 'freight.order.service'
|
|
|
|
line_id = fields.Many2one('freight.order')
|
|
service_id = fields.Many2one('freight.service', required=True)
|
|
partner_id = fields.Many2one('res.partner', string="Vendor")
|
|
qty = fields.Float('Quantity')
|
|
cost = fields.Float('Cost')
|
|
sale = fields.Float('Sale')
|
|
total_sale = fields.Float('Total Sale')
|
|
|
|
@api.onchange('service_id', 'partner_id')
|
|
def _onchange_partner_id(self):
|
|
"""Calculate the price of services"""
|
|
for rec in self:
|
|
if rec.service_id:
|
|
if rec.partner_id:
|
|
if rec.service_id.line_ids:
|
|
for service in rec.service_id.line_ids:
|
|
if rec.partner_id == service.partner_id:
|
|
rec.sale = service.sale
|
|
else:
|
|
rec.sale = rec.service_id.sale_price
|
|
else:
|
|
rec.sale = rec.service_id.sale_price
|
|
else:
|
|
rec.sale = rec.service_id.sale_price
|
|
|
|
@api.onchange('qty', 'sale')
|
|
def _onchange_qty(self):
|
|
"""Calculate the subtotal of route operation"""
|
|
for rec in self:
|
|
rec.total_sale = rec.qty * rec.sale
|
|
|
|
|
|
class Tracking(models.Model):
|
|
_name = 'freight.track'
|
|
|
|
source_loc = fields.Many2one('freight.port', 'Source Location')
|
|
destination_loc = fields.Many2one('freight.port', 'Destination Location')
|
|
transport_type = fields.Selection([('land', 'Land'), ('air', 'Air'),
|
|
('water', 'Water')], "Transport")
|
|
track_id = fields.Many2one('freight.order')
|
|
date = fields.Date('Date')
|
|
type = fields.Selection([('received', 'Received'),
|
|
('delivered', 'Delivered')], 'Received/Delivered')
|
|
|