- Verify your RingCentral credentials are working.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_archived_fusion_faxes/views/res_partner_views.xml b/_archived_fusion_faxes/views/res_partner_views.xml
deleted file mode 100644
index d1aec564..00000000
--- a/_archived_fusion_faxes/views/res_partner_views.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
- res.partner.form.inherit.fusion_faxes
- res.partner
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_archived_fusion_faxes/views/sale_order_views.xml b/_archived_fusion_faxes/views/sale_order_views.xml
deleted file mode 100644
index 8a39ccb1..00000000
--- a/_archived_fusion_faxes/views/sale_order_views.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
- sale.order.form.inherit.fusion_faxes
- sale.order
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_archived_fusion_faxes/wizard/__init__.py b/_archived_fusion_faxes/wizard/__init__.py
deleted file mode 100644
index f5dc5564..00000000
--- a/_archived_fusion_faxes/wizard/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2026 Nexa Systems Inc.
-# License OPL-1 (Odoo Proprietary License v1.0)
-
-from . import send_fax_wizard
-from . import send_fax_wizard_line
diff --git a/_archived_fusion_faxes/wizard/send_fax_wizard.py b/_archived_fusion_faxes/wizard/send_fax_wizard.py
deleted file mode 100644
index 6fd563de..00000000
--- a/_archived_fusion_faxes/wizard/send_fax_wizard.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2026 Nexa Systems Inc.
-# License OPL-1 (Odoo Proprietary License v1.0)
-
-import base64
-import logging
-
-from odoo import api, fields, models, _
-from odoo.exceptions import UserError
-
-_logger = logging.getLogger(__name__)
-
-
-class SendFaxWizard(models.TransientModel):
- _name = 'fusion_faxes.send.fax.wizard'
- _description = 'Send Fax Wizard'
-
- partner_id = fields.Many2one(
- 'res.partner',
- string='Recipient',
- required=True,
- )
- fax_number = fields.Char(
- string='Fax Number',
- required=True,
- )
- cover_page_text = fields.Text(
- string='Cover Page Text',
- )
- document_line_ids = fields.One2many(
- 'fusion_faxes.send.fax.wizard.line',
- 'wizard_id',
- string='Documents',
- )
- generate_pdf = fields.Boolean(
- string='Attach PDF of Source Document',
- default=True,
- help='Automatically generate and attach a PDF of the sale order or invoice.',
- )
-
- # Context links
- sale_order_id = fields.Many2one('sale.order', string='Sale Order')
- account_move_id = fields.Many2one('account.move', string='Invoice')
-
- @api.onchange('partner_id')
- def _onchange_partner_id(self):
- if self.partner_id and self.partner_id.x_ff_fax_number:
- self.fax_number = self.partner_id.x_ff_fax_number
-
- @api.model
- def default_get(self, fields_list):
- res = super().default_get(fields_list)
- context = self.env.context
-
- # Auto-fill from sale order context
- if context.get('active_model') == 'sale.order' and context.get('active_id'):
- order = self.env['sale.order'].browse(context['active_id'])
- res['sale_order_id'] = order.id
- res['partner_id'] = order.partner_id.id
- if order.partner_id.x_ff_fax_number:
- res['fax_number'] = order.partner_id.x_ff_fax_number
-
- # Auto-fill from invoice context
- elif context.get('active_model') == 'account.move' and context.get('active_id'):
- move = self.env['account.move'].browse(context['active_id'])
- res['account_move_id'] = move.id
- res['partner_id'] = move.partner_id.id
- if move.partner_id.x_ff_fax_number:
- res['fax_number'] = move.partner_id.x_ff_fax_number
-
- # Pre-attach documents when forwarding a fax
- forward_ids = context.get('forward_attachment_ids')
- if forward_ids:
- lines = []
- for seq, att_id in enumerate(forward_ids, start=1):
- lines.append((0, 0, {
- 'sequence': seq * 10,
- 'attachment_id': att_id,
- 'file_name': self.env['ir.attachment'].browse(att_id).name,
- }))
- res['document_line_ids'] = lines
- res['generate_pdf'] = False
-
- return res
-
- def _generate_source_pdf(self):
- """Generate a PDF of the linked sale order or invoice."""
- self.ensure_one()
- if self.sale_order_id:
- report = self.env.ref('sale.action_report_saleorder')
- pdf_content, _ = report._render_qweb_pdf(report.id, [self.sale_order_id.id])
- filename = f'{self.sale_order_id.name}.pdf'
- return filename, pdf_content
- elif self.account_move_id:
- report = self.env.ref('account.account_invoices')
- pdf_content, _ = report._render_qweb_pdf(report.id, [self.account_move_id.id])
- filename = f'{self.account_move_id.name}.pdf'
- return filename, pdf_content
- return None, None
-
- def action_send(self):
- """Create a fusion.fax record and send it."""
- self.ensure_one()
-
- if not self.fax_number:
- raise UserError(_('Please enter a fax number.'))
-
- # Collect attachment IDs from ordered wizard lines
- ordered_attachment_ids = list(
- self.document_line_ids.sorted('sequence').mapped('attachment_id').ids
- )
-
- # Generate PDF of source document if requested
- if self.generate_pdf:
- filename, pdf_content = self._generate_source_pdf()
- if pdf_content:
- attachment = self.env['ir.attachment'].create({
- 'name': filename,
- 'type': 'binary',
- 'datas': base64.b64encode(pdf_content),
- 'mimetype': 'application/pdf',
- 'res_model': 'fusion.fax',
- })
- # Generated PDF goes first (sequence 1)
- ordered_attachment_ids.insert(0, attachment.id)
-
- if not ordered_attachment_ids:
- raise UserError(_('Please attach at least one document or enable PDF generation.'))
-
- # Build document lines preserving the wizard ordering
- document_lines = []
- for seq, att_id in enumerate(ordered_attachment_ids, start=1):
- document_lines.append((0, 0, {
- 'sequence': seq * 10,
- 'attachment_id': att_id,
- }))
-
- # Create the fax record
- fax = self.env['fusion.fax'].create({
- 'partner_id': self.partner_id.id,
- 'fax_number': self.fax_number,
- 'cover_page_text': self.cover_page_text,
- 'document_ids': document_lines,
- 'sale_order_id': self.sale_order_id.id if self.sale_order_id else False,
- 'account_move_id': self.account_move_id.id if self.account_move_id else False,
- 'sent_by_id': self.env.user.id,
- })
-
- # Send immediately
- fax._send_fax()
-
- return {
- 'type': 'ir.actions.client',
- 'tag': 'display_notification',
- 'params': {
- 'title': _('Fax Sent'),
- 'message': _('Fax %s sent successfully to %s.') % (fax.name, self.fax_number),
- 'type': 'success',
- 'sticky': False,
- 'next': {'type': 'ir.actions.act_window_close'},
- },
- }
diff --git a/_archived_fusion_faxes/wizard/send_fax_wizard_line.py b/_archived_fusion_faxes/wizard/send_fax_wizard_line.py
deleted file mode 100644
index 6f99a57d..00000000
--- a/_archived_fusion_faxes/wizard/send_fax_wizard_line.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2026 Nexa Systems Inc.
-# License OPL-1 (Odoo Proprietary License v1.0)
-
-from odoo import api, fields, models
-
-
-class SendFaxWizardLine(models.TransientModel):
- _name = 'fusion_faxes.send.fax.wizard.line'
- _description = 'Send Fax Wizard Document Line'
- _order = 'sequence, id'
-
- wizard_id = fields.Many2one(
- 'fusion_faxes.send.fax.wizard',
- string='Wizard',
- required=True,
- ondelete='cascade',
- )
- sequence = fields.Integer(
- string='Order',
- default=10,
- )
- file_upload = fields.Binary(
- string='Upload File',
- )
- file_name = fields.Char(
- string='File Name',
- )
- attachment_id = fields.Many2one(
- 'ir.attachment',
- string='Attachment',
- readonly=True,
- )
-
- @api.onchange('file_upload')
- def _onchange_file_upload(self):
- """Create an ir.attachment when a file is uploaded."""
- if self.file_upload and self.file_name:
- attachment = self.env['ir.attachment'].create({
- 'name': self.file_name,
- 'type': 'binary',
- 'datas': self.file_upload,
- 'res_model': 'fusion.fax',
- })
- self.attachment_id = attachment.id
diff --git a/_archived_fusion_faxes/wizard/send_fax_wizard_views.xml b/_archived_fusion_faxes/wizard/send_fax_wizard_views.xml
deleted file mode 100644
index a6aca76b..00000000
--- a/_archived_fusion_faxes/wizard/send_fax_wizard_views.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
- fusion_faxes.send.fax.wizard.form
- fusion_faxes.send.fax.wizard
-
-
-
-
-
-
-
- Send Fax
- fusion_faxes.send.fax.wizard
- form
- new
-
-
-
-
-
diff --git a/delivery_canadapost-19.0.1.0.0.zip b/delivery_canadapost-19.0.1.0.0.zip
deleted file mode 100644
index 1fb51309..00000000
Binary files a/delivery_canadapost-19.0.1.0.0.zip and /dev/null differ
diff --git a/delivery_canadapost/README.md b/delivery_canadapost/README.md
deleted file mode 100644
index f59beed3..00000000
--- a/delivery_canadapost/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Synodica Solutions
-
-## Changelog
-
-### 16.0.0.0.0
-
-- Initial version
diff --git a/delivery_canadapost/__init__.py b/delivery_canadapost/__init__.py
deleted file mode 100644
index c265faff..00000000
--- a/delivery_canadapost/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from . import models
-from . import canada_post_api
diff --git a/delivery_canadapost/__manifest__.py b/delivery_canadapost/__manifest__.py
deleted file mode 100644
index c59d24c7..00000000
--- a/delivery_canadapost/__manifest__.py
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- # App information
- "name": "Canada Post Shipping",
- "version": "19.0.1.0.0",
- "category": "Inventory/Delivery",
- "summary": "Integration of Canada Post delivery services in Odoo "
- "to handle shipment operations, including get "
- "live rate, generating shipping labels, "
- "retrieving tracking numbers",
- "license": "OPL-1",
- "depends": ["stock_delivery", "mail"],
- # Views
- "data": [
- "data/delivery_canada_post_data.xml",
- "views/delivery_carrier_view.xml",
- ],
- "images": ["static/description/canada_post_banner.gif"],
- # Author
- "author": "Synodica Solutions Pvt. Ltd.",
- "website": "https://synodica.com",
- "maintainer": "Synodica Solutions Pvt. Ltd.",
- "support": "support@synodica.com",
- # Technical
- "installable": True,
- "auto_install": False,
- "application": True,
- "price": "149.00",
- "currency": "USD",
-}
diff --git a/delivery_canadapost/canada_post_api/__init__.py b/delivery_canadapost/canada_post_api/__init__.py
deleted file mode 100644
index e3d3ecd3..00000000
--- a/delivery_canadapost/canada_post_api/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from . import canada_post_response
-from . import utils
\ No newline at end of file
diff --git a/delivery_canadapost/data/delivery_canada_post_data.xml b/delivery_canadapost/data/delivery_canada_post_data.xml
deleted file mode 100644
index 0f30d752..00000000
--- a/delivery_canadapost/data/delivery_canada_post_data.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
- canadapost BOX
- canada_post
- 100
- 100
- 100
- 20.00
-
-
-
-
- Canada Post
- Delivery
- service
-
-
-
- 0.0
-
-
-
- Canada Post
-
- canada_post
- commercial
- SO
- DOM.RP
-
-
-
diff --git a/delivery_canadapost/models/__init__.py b/delivery_canadapost/models/__init__.py
deleted file mode 100644
index 32b5f47d..00000000
--- a/delivery_canadapost/models/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from . import delivery_carrier
-from . import product_packaging
-from . import res_company
diff --git a/delivery_canadapost/models/delivery_carrier.py b/delivery_canadapost/models/delivery_carrier.py
deleted file mode 100644
index 93eb311e..00000000
--- a/delivery_canadapost/models/delivery_carrier.py
+++ /dev/null
@@ -1,509 +0,0 @@
-import string
-import random
-from requests import request
-import xml.etree.ElementTree as etree
-import logging
-_logger = logging.getLogger(__name__)
-from odoo import models, fields, api, _
-from odoo.exceptions import RedirectWarning,ValidationError
-from odoo.addons.delivery_canadapost.canada_post_api.canada_post_response import Response
-
-
-class DeliveryCarrier(models.Model):
- _inherit = 'delivery.carrier'
-
- delivery_type = fields.Selection(selection_add=[('canada_post', 'Canada Post')], ondelete={
- 'canada_post': lambda recs: recs.write({'delivery_type': 'fixed', 'fixed_price': 0})})
- option_code = fields.Selection([('SO','SO - Signature'),
- ('COV','COV - Coverage'),
- ('COD','COD - Collect on delivery'),
- ('PA18','PA18 - Proof of Age Required - 18'),
- ('PA19','PA19 - Proof of Age Required - 19'),
- ('HFP','HFP - Card for pickup'),
- ('DNS','DNS - Do not safe drop'),
- ('LAD','LAD - Leave at door - do not card'),
- ],string="Option Code",
- help="Required if the corresponding parent XML element option exists. This is the option code indicating which option applies to this shipment.\n Note: The D2PO option indicates that the parcel will be delivered directly to a nearby Post Office. For the D2PO option, the following XML elements are required: \n name (under destination) \n client-voice-number (under destination) \n notification \n option-qualifier-2 \n Note: If you select Collect on Delivery (COD), specify Card for Pickup (HFP) or Deliver to Post Office (D2PO). This is to facilitate the collection of COD funds at a post office. If not specified, the system will default to HFP. \n Non-delivery handling codes (required for some U.S.A. and international shipments)\n RASE - Return at Sender’s Expense \n RTS - Return to Sender \n ABAN - Abandon")
- service_type = fields.Selection([('DOM.RP','DOM.RP - Regular Parcel'),
- ('DOM.EP','DOM.EP - Expedited Parcel'),
- ('DOM.XP','DOM.XP - Xpresspost'),
- ('DOM.PC','DOM.PC - Priority'),
- ('USA.XP','USA.XP - Xpresspost USA'),
- ('USA.EP','USA.EP - Expedited Parcel USA'),
- ('INT.IP.SURF','INT.IP.SURF - International Parcel Surface'),
- ('INT.PW.PARCEL','INT.PW.PARCEL - Priority Worldwide parcel Int’l'),
- ('INT.XP','INT.XP - Xpresspost International'),
- ], string="Service Type",
- help="Canada Post delivery service used for shipping the item")
- product_packaging_id = fields.Many2one('stock.package.type', string="Default Package Type",help="Selected packaging type, used in the request parameter")
-
- reason_for_export = fields.Selection([('DOC', 'DOC = document'),
- ('SAM', 'SAM = commercial sample'),
- ('REP', 'REP = repair or warranty'),
- ('SOG', 'SOG = sale of goods'),
- ('OTH', 'OTH = other')], string="Reason For Export",default="SOG",
- help="This is a code that represents the reason for export, which assists with border crossing.")
-
- username = fields.Char("Username", copy=False, help="UserName provided by canada post.")
- password = fields.Char("Password", copy=False, help="Password provided by canada post.")
- customer_number = fields.Char("Customer Number", copy=False, help="The mailed by customer, Customer number provided by canada post.")
- tracking_link = fields.Char(string="Tracking Link",help="Tracking link(URL) useful to track the shipment or package from this URL.",size=256)
-
- canadapost_type = fields.Selection(selection=[('commercial', 'Contract Shipping'), ('counter', 'Non-Contract Shipping')], string="Customer Type", required=False)
- canadapost_contract_id = fields.Char(string="Contract ID")
- canadapost_payment_method = fields.Selection([('CreditCard', 'Credit Card'), ('Account', 'Account')],
- string="Payment Method", default='CreditCard',
- help="This is the method of payment for the shipment. The default value is CreditCard.")
-
- #set default weight_uom_id
- def _default_uom_in_delive(self):
- weight_uom_id = self.env.ref('uom.product_uom_kgm', raise_if_not_found=False)
- if not weight_uom_id:
- uom_categ_id = self.env.ref('uom.product_uom_categ_kgm').id
- weight_uom_id = self.env['uom.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1)
- return weight_uom_id
-
- weight_uom_id = fields.Many2one('uom.uom', string='Shipping UoM according to API UoM',help="Set equivalent unit of measurement according to provider unit of measurement. For Example, if the provider unit of measurement is KG then you have to select KG unit of measurement in the Shipping Unit of Measurement field.",default=_default_uom_in_delive)
-
- #Get Canada post URL
- @api.model
- def get_canadapost_url(self):
- if self.prod_environment:
- return "https://soa-gw.canadapost.ca/rs/"
- else:
- return "https://ct.soa-gw.canadapost.ca/rs/"
-
- def _compute_can_generate_return(self):
- super(DeliveryCarrier, self)._compute_can_generate_return()
- for carrier in self:
- if carrier.delivery_type == 'canada_post':
- carrier.can_generate_return = True
-
- #Check Address filed is validating or not and return boolean value
- @api.model
- def validating_address(self, partner, additional_fields=[]):
- missing_value = []
- mandatory_fields = ['country_id', 'city', 'zip']
- mandatory_fields.extend(additional_fields)
- if not partner.street and not partner.street2 :
- mandatory_fields.append('street')
- for field in mandatory_fields :
- if not getattr(partner, field) :
- missing_value.append(field)
- return missing_value
-
- #Check required value proper or not for Shipping
- def check_required_value_to_ship(self, orders):
- for order in orders :
- if not order.order_line:
- return _("You have not any item to ship. Please provide item first")
- else :
- order_lines_without_weight = order.order_line.filtered(lambda line_item: not line_item.product_id.type in ['service', 'digital'] and not line_item.product_id.weight and not line_item.is_delivery)
- for order_line in order_lines_without_weight :
- return _("Please define weight in product : \n %s") % order_line.product_id.name
-
- # validating customer address
- missing_value = self.validating_address(order.partner_shipping_id)
- if missing_value :
- fields = ", ".join(missing_value)
- return (_("Missing the values of the Customer address. \n Missing field(s) : %s ") % fields)
-
- # validation shipper address
- missing_value = self.validating_address(order.warehouse_id.partner_id)
- if missing_value :
- fields = ", ".join(missing_value)
- return (_("Missing the values of the Warehouse address. \n Missing field(s) : %s ") % fields)
- return False
-
- # Return Weight
- def convert_weight(self,from_uom_unit ,to_uom_unit, weight):
- if not from_uom_unit:
- from_uom_unit = self.env[
- "product.template"
- ]._get_weight_uom_id_from_ir_config_parameter()
- return from_uom_unit._compute_quantity(weight, to_uom_unit)
-
- #Check Validate weight or not
- def check_max_weight(self, order, shipment_weight):
- for order_line in order.order_line:
- if order_line.product_id and order_line.product_id.weight > shipment_weight:
- return (_("Product weight is more than maximum weight."))
- return False
-
- #Get Rate from API than set Rate
- def canada_post_rate_shipment(self, order):
- # check the address validation
- check_value = self.check_required_value_to_ship(order)
- # check the product weight is appropriate to maximum weight.
- if check_value:
- return {'success': False, 'price': 0.0, 'error_message': check_value, 'warning_message': False}
- # check the product weight is appropriate to maximum weight.
- shipment_weight = self.product_packaging_id.max_weight
- check_weight = {}
- if shipment_weight!=0.0:
- check_weight = self.check_max_weight(order, shipment_weight)
- if check_weight:
- return {'success': False, 'price': 0.0, 'error_message': check_weight, 'warning_message': False}
-
- shipper_address = order.warehouse_id.partner_id or order.company_id.partner_id
- recipient_address = order.partner_shipping_id or order.partner_id
- # Convert weight in to the delivery method's weight UOM
- carrier_ctx = self.env.context
- weight = carrier_ctx.get("order_weight", 0.0) or order._get_estimated_weight() or 0.0
- total_weight = self.convert_weight(order.company_id and order.company_id.weight_unit_of_measurement_id,
- self.weight_uom_id,
- weight)
- declared_value = round(order.amount_untaxed, 2)
- declared_currency = order.currency_id.name
- price=0.0
- rate_dict = self.canada_post_get_shipping_rate(shipper_address, recipient_address, total_weight,
- picking_bulk_weight=False, packages=False,
- declared_value=declared_value, declared_currency=declared_currency,
- company_id=order.company_id)
- _logger.info("Rate Response Data : %s" % (rate_dict))
- if rate_dict.get('messages', False):
- return {'success': False, 'price':price, 'error_message': rate_dict['messages']['message']['description'],
- 'warning_message': False}
- if rate_dict.get('price-quotes',False) and rate_dict.get('price-quotes').get('price-quote',False) :
- cnt = 0;
- quotes = rate_dict['price-quotes']['price-quote']
- if isinstance(quotes, dict):
- quotes = [quotes]
- for quote in quotes:
- if quote['service-code']==self.service_type:
- price = quote['price-details']['due']
- cnt+=1
- if cnt==0:
- return {'success': False, 'price': price, 'error_message': "Rate API dosen't provide this service type price",
- 'warning_message': False}
-
- if self.canadapost_type == 'counter' and self.service_type in ['DOM.RP','DOM.EP','DOM.XP','DOM.PC'] and self.option_code == 'COD':
- if order.amount_total + float(price)>= 999.99:
- raise ValidationError(
- _("Get Rate Request Fail : \n The COD amount cannot exceed 1000.00 in Non-Contract Shipping.")
- )
- if self.canadapost_type == 'commercial' and self.service_type in ['DOM.RP','DOM.EP','DOM.XP','DOM.PC'] and self.option_code == 'COD':
- if order.amount_total + float(price) >= 5000.00:
- raise ValidationError(
- _("Get Rate Request Fail : \n The COD amount cannot exceed 5000.00 in Contract Shipping.")
- )
- return {'success': True, 'price': float(price), 'error_message': False, 'warning_message': False}
-
- #Send required XML data and than response from Canada Post API
- def canada_post_get_shipping_rate(self, shipper_address, recipient_address, total_weight, picking_bulk_weight,
- packages=False, declared_value=False, declared_currency=False, company_id=False):
- result = {}
- # built request data
- service_root = etree.Element("mailing-scenario")
-
- if self.canadapost_type == 'commercial':
- service_root.attrib["xmlns"] = "http://www.canadapost.ca/ws/ship/rate-v4"
- else:
- service_root.attrib["xmlns"] = "http://www.canadapost.ca/ws/ship/rate-v3"
- etree.SubElement(service_root, "customer-number").text = self.customer_number
- parcel = etree.SubElement(service_root, "parcel-characteristics")
- etree.SubElement(parcel, "weight").text = str(total_weight)
-
- etree.SubElement(service_root, "origin-postal-code").text = "%s" % (shipper_address.zip.replace(" ","").upper())
-
- destination = etree.SubElement(service_root, "destination")
- if str(self.service_type[:3])=='DOM':
- domestic = etree.SubElement(destination, "domestic")
- etree.SubElement(domestic, "postal-code").text = "%s" % (recipient_address.zip.replace(" ","").upper())
- elif str(self.service_type[:3])=='USA':
- united_states = etree.SubElement(destination, "united-states")
- etree.SubElement(united_states, "zip-code").text = "%s" % (recipient_address.zip.upper())
-
- elif str(self.service_type[:3])=='INT':
- international = etree.SubElement(destination, "international")
- etree.SubElement(international, "country-code").text = "%s" % (recipient_address.country_id and recipient_address.country_id.code)
-
- url='%sship/price'%(self.get_canadapost_url())
-
- base_data = etree.tostring(service_root).decode('utf-8')
-
- if self.canadapost_type == 'commercial':
- headers = {"Accept": "application/vnd.cpc.ship.rate-v4+xml","Content-Type":"application/vnd.cpc.ship.rate-v4+xml"}
- else:
- headers = {"Accept": "application/vnd.cpc.ship.rate-v3+xml","Content-Type":"application/vnd.cpc.ship.rate-v3+xml"}
- _logger.info("Rate Request Data : %s" % (base_data))
- try:
- response_body = request(method='POST', url=url, data=base_data, headers=headers, auth=(self.username,self.password))
- api = Response(response_body)
- result = api.dict()
- _logger.info("Rate Response Data : %s" % (result))
- except Exception as e:
- result['error_message'] = e.message
- return result
- return result
-
- # Random generate string and return
- def get_group_id(self):
- size = 15
- chars = string.ascii_uppercase
- return ''.join(random.choice(chars) for _ in range(size))
-
- # Create Shipment and return generate label and receipt from Canada post API
- def canada_post_send_shipping(self, pickings):
- response = []
- for picking in pickings:
- weight_get = picking.shipping_weight or picking.weight
- from_unit = picking.company_id and picking.company_id.weight_unit_of_measurement_id or ""
- total_weight = round(self.convert_weight(from_unit,
- self.weight_uom_id,
- weight_get), 2)
- # get package type
- package_type = self.product_packaging_id
- for stock_quant in picking.move_line_ids.result_package_id:
- if not stock_quant.package_type_id:
- package_type = self.product_packaging_id
- else:
- package_type = stock_quant.package_type_id
- break
- package_info = self.get_canadapost_parcel(package_type)
- # Get the address of the sender and recipient
- destination_address = picking.partner_id
- sender_address = picking.picking_type_id and picking.picking_type_id.warehouse_id and picking.picking_type_id.warehouse_id.partner_id
- if self.canadapost_type == 'commercial':
- root_node = etree.Element("shipment")
- root_node.attrib["xmlns"] = "http://www.canadapost.ca/ws/shipment-v8"
-
- etree.SubElement(root_node, "transmit-shipment").text ="true"
- etree.SubElement(root_node, "provide-receipt-info").text ="true"
- else:
- root_node = etree.Element("non-contract-shipment")
- root_node.attrib["xmlns"] = "http://www.canadapost.ca/ws/ncshipment-v4"
-
- etree.SubElement(root_node, "requested-shipping-point").text="%s"%(sender_address.zip.replace(" ","").upper() or "")
-
- delivery_spec_node=etree.SubElement(root_node, "delivery-spec")
- etree.SubElement(delivery_spec_node, "service-code").text="%s"%(self.service_type)
-
- sender_node = etree.SubElement(delivery_spec_node, "sender")
- etree.SubElement(sender_node, "company").text=sender_address.name
- etree.SubElement(sender_node, "contact-phone").text ="%s"%(sender_address.phone or "")
- address_details=etree.SubElement(sender_node, "address-details")
- etree.SubElement(address_details, "address-line-1").text =sender_address.street or ""
- etree.SubElement(address_details, "city").text =sender_address.city or ""
- etree.SubElement(address_details, "prov-state").text ="%s"%(sender_address.state_id and sender_address.state_id.code or "")
- if self.canadapost_type == 'commercial':
- etree.SubElement(address_details , "country-code").text = "%s" % (sender_address.country_id and sender_address.country_id.code or "")
- etree.SubElement(address_details, "postal-zip-code").text ="%s"%(sender_address.zip.replace(" ","").upper() or "")
-
- destination_node = etree.SubElement(delivery_spec_node, "destination")
- etree.SubElement(destination_node, "name").text =destination_address.name
- etree.SubElement(destination_node, "company").text =destination_address.name
- etree.SubElement(destination_node, "client-voice-number").text = destination_address.phone
- destination_address_details = etree.SubElement(destination_node, "address-details")
- etree.SubElement(destination_address_details , "address-line-1").text =destination_address.street or ""
- etree.SubElement(destination_address_details , "city").text =destination_address.city or ""
- etree.SubElement(destination_address_details , "prov-state").text ="%s"%(destination_address.state_id and destination_address.state_id.code or "")
- etree.SubElement(destination_address_details , "country-code").text = "%s" % (destination_address.country_id and destination_address.country_id.code or "")
- etree.SubElement(destination_address_details , "postal-zip-code").text ="%s"%(destination_address.zip.replace(" ","").upper() or "")
-
- if self.option_code and not self.service_type in ['USA.XP','USA.EP','INT.IP.SURF','INT.PW.PARCEL','INT.XP']:
- options = etree.SubElement(delivery_spec_node, "options")
- option = etree.SubElement(options, "option")
- etree.SubElement(option , "option-code").text = str(self.option_code or "")
-
-
- if self.option_code in ['PA18','PA19']:
- option_pa = etree.SubElement(options, "option")
- etree.SubElement(option_pa , "option-code").text = 'SO'
-
- if self.option_code == 'COD' or self.option_code == 'COV':
- etree.SubElement(option , "option-amount").text = str(picking.sale_id.amount_total or "")
- etree.SubElement(option , "option-qualifier-1").text ="true"
- else:
- options = etree.SubElement(delivery_spec_node, "options")
- option_usa = etree.SubElement(options, "option")
- etree.SubElement(option_usa , "option-code").text = 'RASE'
-
-
- parcel_characteristics= etree.SubElement(delivery_spec_node, "parcel-characteristics")
- etree.SubElement(parcel_characteristics, "weight").text ="%s"%(total_weight)
-
- dimensions= etree.SubElement(parcel_characteristics, "dimensions ")
- etree.SubElement(dimensions, "length").text ="%s"%(package_info.get('length', 1))
- etree.SubElement(dimensions, "width").text ="%s"%(package_info.get('width', 1))
- etree.SubElement(dimensions, "height").text ="%s"%(package_info.get('height', 1))
-
- preferences= etree.SubElement(delivery_spec_node, "preferences")
- etree.SubElement(preferences, "show-packing-instructions").text ="true"
-
- customs = etree.SubElement(delivery_spec_node, "customs")
- etree.SubElement(customs, "currency").text = str(picking.sale_id.currency_id.name)
- if picking.sale_id.currency_id.rate:
- rate=picking.sale_id.currency_id.rate
- rate=round(rate,2)
- etree.SubElement(customs, "conversion-from-cad").text = str(rate or '')
- etree.SubElement(customs, "reason-for-export").text = "%s"%(self.reason_for_export)
-
- sku_list = etree.SubElement(customs, "sku-list")
- for move_line in picking.move_ids:
- item = etree.SubElement(sku_list,"item")
- etree.SubElement(item, "customs-description").text = str(move_line.product_id.name)
- etree.SubElement(item, "unit-weight").text = str(move_line.weight)
- etree.SubElement(item, "customs-value-per-unit").text = str(move_line.product_id.lst_price)
- etree.SubElement(item, "customs-number-of-units").text = str(int(move_line.product_uom_qty))
-
- if self.canadapost_type == 'commercial':
- settlement_info = etree.SubElement(delivery_spec_node, "settlement-info")
- etree.SubElement(settlement_info, "contract-id").text = self.canadapost_contract_id
- etree.SubElement(settlement_info, "intended-method-of-payment").text = self.canadapost_payment_method
- api_url=self.get_canadapost_url()
- if self.canadapost_type == 'commercial':
- url="%s%s/%s/shipment"%(api_url,self.customer_number,self.customer_number)
- else:
- url="%s%s/ncshipment"%(api_url,self.customer_number)
-
- base_data= etree.tostring(root_node).decode('utf-8')
-
- if self.canadapost_type == 'commercial':
- headers = {"Accept": "application/vnd.cpc.shipment-v8+xml","Content-Type":"application/vnd.cpc.shipment-v8+xml","Accept-language":"en-CA"}
- else:
- headers = {"Accept": "application/vnd.cpc.ncshipment-v4+xml","Content-Type":"application/vnd.cpc.ncshipment-v4+xml","Accept-language":"en-CA"}
- #try:
- _logger.info("Create Shipment Request Data : %s" % (base_data))
- response_body = request(method='POST', url=url, data=base_data, headers=headers, auth=(self.username,self.password))
- if response_body.status_code == 200:
- api = Response(response_body)
- result = api.dict()
- _logger.info("Create Shipment Response Data : %s" % (result))
- else:
- error_code = "%s" % (response_body.status_code)
- error_message = response_body.reason
- message = error_code + " " + error_message
-
- api = Response(response_body)
- result = api.dict()
-
- if result['messages']['message']['description']:
- raise ValidationError(_("ShipmentRequest Fail : \n %s" % (result['messages']['message']['description'])))
- else:
- raise ValidationError(_("ShipmentRequest Fail : %s \n More Information \n %s" % (message, response_body.text)))
- if self.canadapost_type == 'commercial':
- if not result['shipment-info']['shipment-id'] or not result['shipment-info']['links']['link']:
- raise RedirectWarning("ShipmentRequest Fail \n More Information \n %s" % (result))
-
- shipment_id=str(result['shipment-info']['shipment-id'])
- else:
- if not result['non-contract-shipment-info']['shipment-id'] or not result['non-contract-shipment-info']['links']['link']:
- raise RedirectWarning("ShipmentRequest Fail \n More Information \n %s" % (result))
-
- shipment_id=str(result['non-contract-shipment-info']['shipment-id'])
- commercial_invoice_url_attchment=""
- commercial_invoice = False
- url_attchment=""
-
- if self.canadapost_type == 'commercial':
- for link in result['shipment-info']['links']['link']:
- if link['_rel'] == 'label':
- url_attchment = link['_href']
- if link['_rel'] =='commercialInvoice':
- commercial_invoice_url_attchment = link['_href']
- commercial_invoice=True
- else:
- for link in result['non-contract-shipment-info']['links']['link']:
- if link['_rel'] == 'label':
- url_attchment = link['_href']
- if link['_rel'] =='commercialInvoice':
- commercial_invoice_url_attchment = link['_href']
- commercial_invoice=True
- headers_attchment = {'Accept': 'application/pdf'}
- try:
- attachment_response = request(method='GET', url=url_attchment, headers=headers_attchment, auth=(
- self.username, self.password))
- _logger.info("Label Response Data : %s" % (attachment_response))
- picking.message_post(attachments=[
- ('Shipment Label - %s.PDF' % (shipment_id),
- attachment_response.content)])
-
- if commercial_invoice:
- commercial_invoice_attachment_response = request(method='GET', url=commercial_invoice_url_attchment, headers=headers_attchment, auth=(
- self.username, self.password))
- picking.message_post(attachments=[
- ('Shipment Commercial Invoice - %s.PDF' % (shipment_id),
- commercial_invoice_attachment_response.content)])
- except Exception as e:
- raise RedirectWarning(e)
- if self.canadapost_type == 'commercial':
- headers = {"Accept": "application/vnd.cpc.shipment-v8+xml","Accept-language":"en-CA"}
- url_receipt = "%s"%(self.get_canadapost_url())+ str(self.customer_number)+"/"+str(self.customer_number)+"/shipment/" + str(shipment_id) + "/receipt"
- else:
- url_receipt = "%s"%(self.get_canadapost_url())+ str(self.customer_number) +"/ncshipment/" + str(shipment_id) + "/receipt"
- try:
- receipt_response= request(method='GET', url=url_receipt, headers=headers, auth=(self.username,self.password))
- if receipt_response.status_code == 200:
- api_receipt = Response(receipt_response)
- result_receipt = api_receipt.dict()
- _logger.info("Get shipment Detail Response Data : %s" % (result_receipt))
- else:
- result_receipt = {}
- error_code = "%s" % (receipt_response.status_code)
- error_message = response_body.reason
- message = error_code + " " + error_message
- mesage="ShipmentAcceptRequest Fail : %s \n More Information \n %s" % (message, response_body.text)
- picking.message_post(body=mesage)
- except Exception as e:
- picking.message_post(body=e)
- if self.canadapost_type == 'commercial':
- if result_receipt:
- extra_price = result_receipt['shipment-receipt'] and result_receipt['shipment-receipt']['cc-receipt-details'] and result_receipt['shipment-receipt']['cc-receipt-details']['charge-amount'] or 0.0
- else:
- extra_price = 0.0
- tracking_pin= result['shipment-info'].get('tracking-pin',False)
- else:
- if result_receipt:
- extra_price = result_receipt['non-contract-shipment-receipt'] and result_receipt['non-contract-shipment-receipt']['cc-receipt-details'] and result_receipt['non-contract-shipment-receipt']['cc-receipt-details']['charge-amount'] or 0.0
- else:
- extra_price = 0.0
- tracking_pin= result['non-contract-shipment-info'].get('tracking-pin',False)
- if not tracking_pin:
- _logger.info("This Service not provide the tracking no.Service is : %s"%self.service_type)
- shipping_data = {
- 'exact_price': float(extra_price) or 0.0,
- 'tracking_number': tracking_pin}
- response += [shipping_data]
- return response
-
- # Tracking link return
- def canada_post_get_tracking_link(self, picking):
- link = picking.carrier_id.tracking_link or 'https://www.canadapost.ca/trackweb/en#/resultList?searchFor='
- res = '%s %s' % (link, picking.carrier_tracking_ref)
- return res
-
- def canada_post_cancel_shipment(self, picking):
- raise ValidationError(_("Canada Post does not provide Shipment Cancel API!"))
-
- def get_canadapost_parcel(self, package):
- packaging_length = (
- self.sudo()._canadapost_convert_dimension_to_uom(
- package.packaging_length, package.length_uom_name
- )
- )
- width = (
- self.sudo()._canadapost_convert_dimension_to_uom(
- package.width, package.length_uom_name
- )
- )
- height = (
- self.sudo()._canadapost_convert_dimension_to_uom(
- package.height, package.length_uom_name
- )
- )
- return {
- "length": packaging_length,
- "width": width,
- "height": height
- }
-
- def _canadapost_convert_dimension_to_uom(self, dimension, length_uom_name):
- target_uom = self.env.ref("uom.product_uom_cm")
- from_uom = self.env["uom.uom"].sudo().search([("name", "=", length_uom_name)])
- if not from_uom:
- from_uom = self.env[
- "product.template"
- ]._get_length_uom_id_from_ir_config_parameter()
- # Convert dimensions
- return from_uom._compute_quantity(dimension, target_uom)
\ No newline at end of file
diff --git a/delivery_canadapost/models/product_packaging.py b/delivery_canadapost/models/product_packaging.py
deleted file mode 100755
index 75430b08..00000000
--- a/delivery_canadapost/models/product_packaging.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from odoo import fields, models
-
-
-class PackageType(models.Model):
- _inherit = "stock.package.type"
-
- package_carrier_type = fields.Selection([('canada_post', 'Canada Post')])
\ No newline at end of file
diff --git a/delivery_canadapost/static/description/GoShippo.gif b/delivery_canadapost/static/description/GoShippo.gif
deleted file mode 100644
index 03706a79..00000000
Binary files a/delivery_canadapost/static/description/GoShippo.gif and /dev/null differ
diff --git a/delivery_canadapost/static/description/Shippit.gif b/delivery_canadapost/static/description/Shippit.gif
deleted file mode 100644
index 5fa90955..00000000
Binary files a/delivery_canadapost/static/description/Shippit.gif and /dev/null differ
diff --git a/delivery_canadapost/static/description/advanced_search.gif b/delivery_canadapost/static/description/advanced_search.gif
deleted file mode 100644
index 9bd02bc9..00000000
Binary files a/delivery_canadapost/static/description/advanced_search.gif and /dev/null differ
diff --git a/delivery_canadapost/static/description/canada_post_005.png b/delivery_canadapost/static/description/canada_post_005.png
deleted file mode 100644
index 704c0f69..00000000
Binary files a/delivery_canadapost/static/description/canada_post_005.png and /dev/null differ
diff --git a/delivery_canadapost/static/description/canadapost_thumbnail.png b/delivery_canadapost/static/description/canadapost_thumbnail.png
deleted file mode 100644
index eec64f0e..00000000
Binary files a/delivery_canadapost/static/description/canadapost_thumbnail.png and /dev/null differ
diff --git a/delivery_canadapost/static/description/chitchat_shipping.gif b/delivery_canadapost/static/description/chitchat_shipping.gif
deleted file mode 100644
index 57b62261..00000000
Binary files a/delivery_canadapost/static/description/chitchat_shipping.gif and /dev/null differ
diff --git a/delivery_canadapost/static/description/company_icon.png b/delivery_canadapost/static/description/company_icon.png
deleted file mode 100644
index e539a4a4..00000000
Binary files a/delivery_canadapost/static/description/company_icon.png and /dev/null differ
diff --git a/delivery_canadapost/static/description/dpd.gif b/delivery_canadapost/static/description/dpd.gif
deleted file mode 100644
index d326b372..00000000
Binary files a/delivery_canadapost/static/description/dpd.gif and /dev/null differ
diff --git a/delivery_canadapost/static/description/dsv_banner.gif b/delivery_canadapost/static/description/dsv_banner.gif
deleted file mode 100644
index 8efeddcc..00000000
Binary files a/delivery_canadapost/static/description/dsv_banner.gif and /dev/null differ
diff --git a/delivery_canadapost/static/description/icon.png b/delivery_canadapost/static/description/icon.png
deleted file mode 100644
index 4e9f7f83..00000000
Binary files a/delivery_canadapost/static/description/icon.png and /dev/null differ
diff --git a/delivery_canadapost/static/description/index.html b/delivery_canadapost/static/description/index.html
deleted file mode 100644
index a37d2be2..00000000
--- a/delivery_canadapost/static/description/index.html
+++ /dev/null
@@ -1,321 +0,0 @@
-
-
-
-
-
-
-
- Canada Post Shipping Integration with Odoo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Odoo Canada Post Shipping Integration
-
-
-
-
-
-
-
-
-
- Why Canada Post Odoo Shipping Integration App From Synodica?
-
-
- Integrate Canada Post streamline shipping processes by obtaining real-time courier quotes for
- various services, generating shipping labels based on order details, and providing tracking
- numbers with
- links for shipment updates
- ensuring a seamless and efficient logistics workflow
-
- The error occurred while rendering the template fusion_quotations.portal_quotation_list
- and evaluating the following expression: <t t-out="dict(a._fields[\'equipment_type\'].selection).get(a.equipment_type, a.equipment_type or \'\')"/>
-
-
Error while rendering the template:
- ValueError: dictionary update sequence element #0 has length 1; 2 is required
- Template: fusion_quotations.portal_quotation_list
- Reference: 13488
- Path: /t/t/div/t[2]/table/tbody/t/tr/td[3]/t
- Element: <t t-out="dict(a._fields[\'equipment_type\'].selection).get(a.equipment_type, a.equipment_type or \'\')"/>
- From: (13488, '/t/t', '<t t-call="portal.portal_layout"/>')
- (13488, '/t/t/div/t[2]/table/tbody/t/tr/td[3]/t', '<t t-out="dict(a._fields[\\\'equipment_type\\\'].selection).get(a.equipment_type, a.equipment_type or \\\'\\\')"/>')
Traceback (most recent call last):
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_qweb.py", line 753, in _render_iterall
- for item in frame.iterator:
- File "<13488>", line 152, in template_fusion_quotations_portal_quotation_list_13488_t_call_0
-ValueError: dictionary update sequence element #0 has length 1; 2 is required
-
-The above exception was the direct cause of the following exception:
-
-Traceback (most recent call last):
- File "/usr/lib/python3/dist-packages/odoo/http.py", line 2275, in _serve_db
- return service_model.retrying(serve_func, env=self.env)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/service/model.py", line 184, in retrying
- result = func()
- ^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/http.py", line 2330, in _serve_ir_http
- response = self.dispatcher.dispatch(rule.endpoint, args)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/http.py", line 2452, in dispatch
- return self.request.registry['ir.http']._dispatch(endpoint)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_http.py", line 357, in _dispatch
- result.flatten()
- File "/usr/lib/python3/dist-packages/odoo/tools/facade.py", line 83, in wrap_func
- func(self._wrapped__, *args, **kwargs)
- File "/usr/lib/python3/dist-packages/odoo/http.py", line 1546, in flatten
- self.response.append(self.render())
- ^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/http.py", line 1538, in render
- return request.env["ir.ui.view"]._render_template(self.template, self.qcontext)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/addons/website/models/ir_ui_view.py", line 456, in _render_template
- return super()._render_template(template, values=values)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_ui_view.py", line 2531, in _render_template
- return self.env['ir.qweb']._render(template, values)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/mnt/enterprise-addons/web_studio/models/ir_qweb.py", line 14, in _render
- return super()._render(template, values, **options)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_qweb.py", line 725, in _render
- return Markup(''.join(iterator))
- ^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_qweb.py", line 753, in _render_iterall
- for item in frame.iterator:
- File "<13488>", line 264, in template_fusion_quotations_portal_quotation_list_13488
- File "<13488>", line 250, in template_fusion_quotations_portal_quotation_list_13488_content
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_qweb.py", line 616, in __str__
- self.html = ''.join(self.irQweb._render_iterall(
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- File "/usr/lib/python3/dist-packages/odoo/addons/base/models/ir_qweb.py", line 847, in _render_iterall
- raise QWebError(qweb_error_info) from error
-odoo.addons.base.models.ir_qweb.QWebError: Error while rendering the template:
- ValueError: dictionary update sequence element #0 has length 1; 2 is required
- Template: fusion_quotations.portal_quotation_list
- Reference: 13488
- Path: /t/t/div/t[2]/table/tbody/t/tr/td[3]/t
- Element: <t t-out="dict(a._fields[\'equipment_type\'].selection).get(a.equipment_type, a.equipment_type or \'\')"/>
- From: (13488, '/t/t', '<t t-call="portal.portal_layout"/>')
- (13488, '/t/t/div/t[2]/table/tbody/t/tr/td[3]/t', '<t t-out="dict(a._fields[\\\'equipment_type\\\'].selection).get(a.equipment_type, a.equipment_type or \\\'\\\')"/>')
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/fusion_authorizer_portal/__manifest__.py b/fusion_authorizer_portal/__manifest__.py
index 6b732bc8..0dc4352f 100644
--- a/fusion_authorizer_portal/__manifest__.py
+++ b/fusion_authorizer_portal/__manifest__.py
@@ -79,7 +79,6 @@ This module provides external portal access for:
'views/portal_accessibility_forms.xml',
'views/portal_technician_templates.xml',
'views/portal_book_assessment.xml',
- 'views/portal_repair_form.xml',
'views/portal_schedule.xml',
'views/portal_page11_sign_templates.xml',
],
diff --git a/fusion_authorizer_portal/controllers/__init__.py b/fusion_authorizer_portal/controllers/__init__.py
index c8d40284..ce12d228 100644
--- a/fusion_authorizer_portal/controllers/__init__.py
+++ b/fusion_authorizer_portal/controllers/__init__.py
@@ -3,6 +3,5 @@
from . import portal_main
from . import portal_assessment
from . import pdf_editor
-from . import portal_repair
from . import portal_schedule
from . import portal_page11_sign
\ No newline at end of file
diff --git a/Fusion Backend Theme/fusion_backend_theme/__init__.py b/fusion_backend_theme/__init__.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/__init__.py
rename to fusion_backend_theme/__init__.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/__manifest__.py b/fusion_backend_theme/__manifest__.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/__manifest__.py
rename to fusion_backend_theme/__manifest__.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/controllers/__init__.py b/fusion_backend_theme/controllers/__init__.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/controllers/__init__.py
rename to fusion_backend_theme/controllers/__init__.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/controllers/main.py b/fusion_backend_theme/controllers/main.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/controllers/main.py
rename to fusion_backend_theme/controllers/main.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/__init__.py b/fusion_backend_theme/models/__init__.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/__init__.py
rename to fusion_backend_theme/models/__init__.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/color_editor.py b/fusion_backend_theme/models/color_editor.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/color_editor.py
rename to fusion_backend_theme/models/color_editor.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/ir_http.py b/fusion_backend_theme/models/ir_http.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/ir_http.py
rename to fusion_backend_theme/models/ir_http.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/res_company.py b/fusion_backend_theme/models/res_company.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/res_company.py
rename to fusion_backend_theme/models/res_company.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/res_config_settings.py b/fusion_backend_theme/models/res_config_settings.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/res_config_settings.py
rename to fusion_backend_theme/models/res_config_settings.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/res_users.py b/fusion_backend_theme/models/res_users.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/res_users.py
rename to fusion_backend_theme/models/res_users.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/models/res_users_settings.py b/fusion_backend_theme/models/res_users_settings.py
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/models/res_users_settings.py
rename to fusion_backend_theme/models/res_users_settings.py
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/description/banner.png b/fusion_backend_theme/static/description/banner.png
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/description/banner.png
rename to fusion_backend_theme/static/description/banner.png
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/description/icon.png b/fusion_backend_theme/static/description/icon.png
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/description/icon.png
rename to fusion_backend_theme/static/description/icon.png
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/description/icon.svg b/fusion_backend_theme/static/description/icon.svg
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/description/icon.svg
rename to fusion_backend_theme/static/description/icon.svg
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/img/background.png b/fusion_backend_theme/static/img/background.png
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/img/background.png
rename to fusion_backend_theme/static/img/background.png
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/chatter/chatter.js b/fusion_backend_theme/static/src/chatter/chatter.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/chatter/chatter.js
rename to fusion_backend_theme/static/src/chatter/chatter.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/chatter/chatter.scss b/fusion_backend_theme/static/src/chatter/chatter.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/chatter/chatter.scss
rename to fusion_backend_theme/static/src/chatter/chatter.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/chatter/chatter.xml b/fusion_backend_theme/static/src/chatter/chatter.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/chatter/chatter.xml
rename to fusion_backend_theme/static/src/chatter/chatter.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/color_scheme/color_scheme_service.js b/fusion_backend_theme/static/src/core/color_scheme/color_scheme_service.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/color_scheme/color_scheme_service.js
rename to fusion_backend_theme/static/src/core/color_scheme/color_scheme_service.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/dialog.js b/fusion_backend_theme/static/src/core/dialog/dialog.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/dialog.js
rename to fusion_backend_theme/static/src/core/dialog/dialog.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/dialog.scss b/fusion_backend_theme/static/src/core/dialog/dialog.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/dialog.scss
rename to fusion_backend_theme/static/src/core/dialog/dialog.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/dialog.xml b/fusion_backend_theme/static/src/core/dialog/dialog.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/dialog.xml
rename to fusion_backend_theme/static/src/core/dialog/dialog.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/select_create_dialog.js b/fusion_backend_theme/static/src/core/dialog/select_create_dialog.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/dialog/select_create_dialog.js
rename to fusion_backend_theme/static/src/core/dialog/select_create_dialog.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/thread/thread.js b/fusion_backend_theme/static/src/core/thread/thread.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/thread/thread.js
rename to fusion_backend_theme/static/src/core/thread/thread.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/core/thread/thread.xml b/fusion_backend_theme/static/src/core/thread/thread.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/core/thread/thread.xml
rename to fusion_backend_theme/static/src/core/thread/thread.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/scss/colors.scss b/fusion_backend_theme/static/src/scss/colors.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/scss/colors.scss
rename to fusion_backend_theme/static/src/scss/colors.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/scss/colors_dark.scss b/fusion_backend_theme/static/src/scss/colors_dark.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/scss/colors_dark.scss
rename to fusion_backend_theme/static/src/scss/colors_dark.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/scss/colors_light.scss b/fusion_backend_theme/static/src/scss/colors_light.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/scss/colors_light.scss
rename to fusion_backend_theme/static/src/scss/colors_light.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/scss/helpers.scss b/fusion_backend_theme/static/src/scss/helpers.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/scss/helpers.scss
rename to fusion_backend_theme/static/src/scss/helpers.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/scss/primary_variables.dark.scss b/fusion_backend_theme/static/src/scss/primary_variables.dark.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/scss/primary_variables.dark.scss
rename to fusion_backend_theme/static/src/scss/primary_variables.dark.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/scss/primary_variables.scss b/fusion_backend_theme/static/src/scss/primary_variables.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/scss/primary_variables.scss
rename to fusion_backend_theme/static/src/scss/primary_variables.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form.dark.scss b/fusion_backend_theme/static/src/views/form/form.dark.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form.dark.scss
rename to fusion_backend_theme/static/src/views/form/form.dark.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form.scss b/fusion_backend_theme/static/src/views/form/form.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form.scss
rename to fusion_backend_theme/static/src/views/form/form.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form_compiler.js b/fusion_backend_theme/static/src/views/form/form_compiler.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form_compiler.js
rename to fusion_backend_theme/static/src/views/form/form_compiler.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form_renderer.js b/fusion_backend_theme/static/src/views/form/form_renderer.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/form/form_renderer.js
rename to fusion_backend_theme/static/src/views/form/form_renderer.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/search/auto_refresh.js b/fusion_backend_theme/static/src/views/search/auto_refresh.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/search/auto_refresh.js
rename to fusion_backend_theme/static/src/views/search/auto_refresh.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/search/auto_refresh.xml b/fusion_backend_theme/static/src/views/search/auto_refresh.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/search/auto_refresh.xml
rename to fusion_backend_theme/static/src/views/search/auto_refresh.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/search/collapse_groups.js b/fusion_backend_theme/static/src/views/search/collapse_groups.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/search/collapse_groups.js
rename to fusion_backend_theme/static/src/views/search/collapse_groups.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/search/collapse_groups.xml b/fusion_backend_theme/static/src/views/search/collapse_groups.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/search/collapse_groups.xml
rename to fusion_backend_theme/static/src/views/search/collapse_groups.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/search/expand_groups.js b/fusion_backend_theme/static/src/views/search/expand_groups.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/search/expand_groups.js
rename to fusion_backend_theme/static/src/views/search/expand_groups.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/views/search/expand_groups.xml b/fusion_backend_theme/static/src/views/search/expand_groups.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/views/search/expand_groups.xml
rename to fusion_backend_theme/static/src/views/search/expand_groups.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.dark.scss b/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.dark.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.dark.scss
rename to fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.dark.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.js b/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.js
rename to fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.scss b/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.scss
rename to fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.xml b/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.xml
rename to fusion_backend_theme/static/src/webclient/app_switcher/app_switcher.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.dark.scss b/fusion_backend_theme/static/src/webclient/navbar/navbar.dark.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.dark.scss
rename to fusion_backend_theme/static/src/webclient/navbar/navbar.dark.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.js b/fusion_backend_theme/static/src/webclient/navbar/navbar.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.js
rename to fusion_backend_theme/static/src/webclient/navbar/navbar.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.scss b/fusion_backend_theme/static/src/webclient/navbar/navbar.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.scss
rename to fusion_backend_theme/static/src/webclient/navbar/navbar.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.xml b/fusion_backend_theme/static/src/webclient/navbar/navbar.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/navbar/navbar.xml
rename to fusion_backend_theme/static/src/webclient/navbar/navbar.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/menu_service.js b/fusion_backend_theme/static/src/webclient/sidebar/menu_service.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/menu_service.js
rename to fusion_backend_theme/static/src/webclient/sidebar/menu_service.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.dark.scss b/fusion_backend_theme/static/src/webclient/sidebar/sidebar.dark.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.dark.scss
rename to fusion_backend_theme/static/src/webclient/sidebar/sidebar.dark.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.js b/fusion_backend_theme/static/src/webclient/sidebar/sidebar.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.js
rename to fusion_backend_theme/static/src/webclient/sidebar/sidebar.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.scss b/fusion_backend_theme/static/src/webclient/sidebar/sidebar.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.scss
rename to fusion_backend_theme/static/src/webclient/sidebar/sidebar.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.xml b/fusion_backend_theme/static/src/webclient/sidebar/sidebar.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/sidebar/sidebar.xml
rename to fusion_backend_theme/static/src/webclient/sidebar/sidebar.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/webclient.js b/fusion_backend_theme/static/src/webclient/webclient.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/webclient.js
rename to fusion_backend_theme/static/src/webclient/webclient.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/webclient.scss b/fusion_backend_theme/static/src/webclient/webclient.scss
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/webclient.scss
rename to fusion_backend_theme/static/src/webclient/webclient.scss
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/src/webclient/webclient.xml b/fusion_backend_theme/static/src/webclient/webclient.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/src/webclient/webclient.xml
rename to fusion_backend_theme/static/src/webclient/webclient.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/tests/group.test.js b/fusion_backend_theme/static/tests/group.test.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/tests/group.test.js
rename to fusion_backend_theme/static/tests/group.test.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/static/tests/refresh.test.js b/fusion_backend_theme/static/tests/refresh.test.js
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/static/tests/refresh.test.js
rename to fusion_backend_theme/static/tests/refresh.test.js
diff --git a/Fusion Backend Theme/fusion_backend_theme/templates/web_layout.xml b/fusion_backend_theme/templates/web_layout.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/templates/web_layout.xml
rename to fusion_backend_theme/templates/web_layout.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/templates/webclient.xml b/fusion_backend_theme/templates/webclient.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/templates/webclient.xml
rename to fusion_backend_theme/templates/webclient.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/views/res_config_settings_views.xml b/fusion_backend_theme/views/res_config_settings_views.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/views/res_config_settings_views.xml
rename to fusion_backend_theme/views/res_config_settings_views.xml
diff --git a/Fusion Backend Theme/fusion_backend_theme/views/res_users_views.xml b/fusion_backend_theme/views/res_users_views.xml
similarity index 100%
rename from Fusion Backend Theme/fusion_backend_theme/views/res_users_views.xml
rename to fusion_backend_theme/views/res_users_views.xml
diff --git a/fusion_canada_post/__manifest__.py b/fusion_canada_post/__manifest__.py
index 0dd92450..b0eeb31b 100644
--- a/fusion_canada_post/__manifest__.py
+++ b/fusion_canada_post/__manifest__.py
@@ -13,11 +13,13 @@
"security/ir.model.access.csv",
"data/ir_sequence_data.xml",
"data/fusion_canada_post_data.xml",
+ "data/ir_cron_data.xml",
"views/fusion_cp_shipment_views.xml",
"views/delivery_carrier_view.xml",
"views/choose_delivery_carrier_views.xml",
"views/sale_order_views.xml",
"views/stock_picking_views.xml",
+ "views/report_templates.xml",
"views/menus.xml",
"views/res_config_settings_views.xml",
],
diff --git a/fusion_canada_post/data/ir_cron_data.xml b/fusion_canada_post/data/ir_cron_data.xml
new file mode 100644
index 00000000..387ed7b5
--- /dev/null
+++ b/fusion_canada_post/data/ir_cron_data.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ Canada Post: Refresh Tracking
+
+ code
+ model._cron_refresh_tracking()
+ 8
+ hours
+ True
+
+
+
diff --git a/fusion_canada_post/models/fusion_cp_shipment.py b/fusion_canada_post/models/fusion_cp_shipment.py
index 35ee7f93..59766873 100644
--- a/fusion_canada_post/models/fusion_cp_shipment.py
+++ b/fusion_canada_post/models/fusion_cp_shipment.py
@@ -1,3 +1,4 @@
+import base64
import logging
from datetime import datetime as dt_mod
from lxml import etree
@@ -66,6 +67,7 @@ class FusionCpShipment(models.Model):
('confirmed', 'Confirmed'),
('shipped', 'Shipped'),
('delivered', 'Delivered'),
+ ('returned', 'Returned'),
('cancelled', 'Cancelled'),
],
string='Status',
@@ -95,10 +97,21 @@ class FusionCpShipment(models.Model):
string='Commercial Invoice',
ondelete='set null',
)
+ # Return label
+ return_tracking_number = fields.Char(
+ string='Return Tracking Number',
+ readonly=True,
+ copy=False,
+ )
+ return_label_attachment_id = fields.Many2one(
+ 'ir.attachment',
+ string='Return Label',
+ ondelete='set null',
+ )
# Shipment details
- shipping_cost = fields.Float(
+ shipping_cost = fields.Monetary(
string='Shipping Cost',
- digits='Product Price',
+ currency_field='currency_id',
readonly=True,
)
service_type = fields.Char(
@@ -141,6 +154,11 @@ class FusionCpShipment(models.Model):
string='Company',
default=lambda self: self.env.company,
)
+ currency_id = fields.Many2one(
+ 'res.currency',
+ related='company_id.currency_id',
+ store=True,
+ )
# Tracking history
tracking_event_ids = fields.One2many(
'fusion.cp.tracking.event',
@@ -369,15 +387,31 @@ class FusionCpShipment(models.Model):
delivered_date = detail.get('actual-delivery-date', '')
if delivered_date:
- self.status = 'delivered'
+ if self._has_return_events():
+ self.status = 'returned'
+ else:
+ self.status = 'delivered'
try:
self.delivery_date = dt_mod.strptime(
delivered_date, '%Y-%m-%d')
except (ValueError, TypeError):
self.delivery_date = fields.Datetime.now()
+ elif self._has_return_events():
+ self.status = 'returned'
elif self.status == 'confirmed' and self.tracking_event_ids:
self.status = 'shipped'
+ def _has_return_events(self):
+ """Check if any tracking events indicate a return/RTS."""
+ RETURN_TYPES = {'RTS', 'RETURN', 'RTS_LABEL_PROC'}
+ for event in self.tracking_event_ids:
+ if event.event_type in RETURN_TYPES:
+ return True
+ desc = (event.event_description or '').lower()
+ if 'return to sender' in desc or 'item returned' in desc:
+ return True
+ return False
+
# ── Void & Reissue ─────────────────────────────────────
def action_void_shipment(self):
@@ -504,3 +538,182 @@ class FusionCpShipment(models.Model):
'url': '%s%s' % (base_link, self.tracking_number),
'target': 'new',
}
+
+ # ── Cron ─────────────────────────────────────────────────
+
+ @api.model
+ def _cron_refresh_tracking(self):
+ """Auto-refresh tracking for all active (non-terminal) shipments."""
+ shipments = self.search([
+ ('status', 'in', ('confirmed', 'shipped')),
+ ('tracking_number', '!=', False),
+ ])
+ _logger.info(
+ "Cron: refreshing tracking for %d shipments", len(shipments))
+ for shipment in shipments:
+ try:
+ shipment.action_refresh_tracking()
+ self.env.cr.commit()
+ except Exception as e:
+ self.env.cr.rollback()
+ _logger.warning(
+ "Cron: tracking refresh failed for %s: %s",
+ shipment.name, str(e))
+
+ # ── Return Labels ────────────────────────────────────────
+
+ def action_view_return_label(self):
+ return self._action_open_attachment(self.return_label_attachment_id)
+
+ def action_create_return_label(self):
+ """Create a prepaid return label via Canada Post Authorized
+ Returns API. Bill-on-scan: charged only when the customer
+ uses the label at a post office.
+ """
+ self.ensure_one()
+ if self.return_tracking_number:
+ raise ValidationError(
+ _("A return label has already been created "
+ "for this shipment."))
+ if not self.carrier_id:
+ raise ValidationError(
+ _("No carrier linked to this shipment."))
+ if not self.picking_id:
+ raise ValidationError(
+ _("No transfer linked to this shipment."))
+
+ carrier = self.carrier_id
+ customer = carrier.customer_number
+
+ warehouse_partner = (
+ self.picking_id.picking_type_id.warehouse_id.partner_id)
+ if not warehouse_partner:
+ raise ValidationError(
+ _("No warehouse address found. Please configure "
+ "a contact on the warehouse."))
+
+ if carrier.prod_environment:
+ base = "https://soa-gw.canadapost.ca"
+ else:
+ base = "https://ct.soa-gw.canadapost.ca"
+
+ url = "%s/rs/%s/%s/authorizedreturn" % (base, customer, customer)
+
+ xml = self._build_return_label_xml(carrier, warehouse_partner)
+ headers = {
+ 'Content-Type': 'application/vnd.cpc.authreturn-v2+xml',
+ 'Accept': 'application/vnd.cpc.authreturn-v2+xml',
+ 'Accept-language': 'en-CA',
+ }
+
+ try:
+ resp = request(
+ method='POST', url=url, data=xml,
+ headers=headers,
+ auth=(carrier.username, carrier.password))
+ _logger.info(
+ "Return Label API %s → %s", url, resp.status_code)
+
+ if resp.status_code not in (200, 201):
+ self._parse_cp_error_response(resp)
+
+ self._process_return_label_response(resp, carrier)
+
+ except ValidationError:
+ raise
+ except Exception as e:
+ raise ValidationError(
+ _("Failed to create return label: %s") % str(e))
+
+ def _build_return_label_xml(self, carrier, return_to_partner):
+ """Build Authorized Return XML.
+
+ return_to_partner = warehouse address (where the return goes).
+ """
+ ns = "http://www.canadapost.ca/ws/authreturn-v2"
+ root = etree.Element("authorized-return", xmlns=ns)
+
+ etree.SubElement(root, "service-code").text = "DOM.EP"
+
+ # Receiver — warehouse where the item is returned to
+ receiver = etree.SubElement(root, "receiver")
+ rec_name = etree.SubElement(receiver, "name")
+ rec_name.text = (return_to_partner.name or '')[:44]
+ rec_company = etree.SubElement(receiver, "company")
+ rec_company.text = (
+ return_to_partner.commercial_company_name
+ or return_to_partner.name or '')[:44]
+ rec_addr = etree.SubElement(receiver, "domestic-address")
+ etree.SubElement(rec_addr, "address-line-1").text = (
+ return_to_partner.street or '')[:44]
+ if return_to_partner.street2:
+ etree.SubElement(rec_addr, "address-line-2").text = (
+ return_to_partner.street2)[:44]
+ etree.SubElement(rec_addr, "city").text = (
+ return_to_partner.city or '')[:40]
+ etree.SubElement(rec_addr, "province").text = (
+ return_to_partner.state_id.code or '')
+ etree.SubElement(rec_addr, "postal-code").text = (
+ (return_to_partner.zip or '').replace(' ', ''))
+
+ # Parcel characteristics
+ parcel = etree.SubElement(root, "parcel-characteristics")
+ etree.SubElement(parcel, "weight").text = str(
+ max(self.weight or 0.5, 0.1))
+
+ # Print preferences
+ prefs = etree.SubElement(root, "print-preferences")
+ etree.SubElement(prefs, "output-format").text = (
+ carrier.fusion_cp_output_format or '8.5x11')
+
+ return etree.tostring(
+ root, xml_declaration=True, encoding='UTF-8')
+
+ def _process_return_label_response(self, resp, carrier):
+ """Parse return label response, download label PDF,
+ store on shipment.
+ """
+ api_resp = Response(resp)
+ result = api_resp.dict()
+
+ auth_return = result.get('authorized-return', result)
+ tracking_pin = auth_return.get('tracking-pin', '')
+ self.return_tracking_number = tracking_pin
+
+ # Get label artifact URL from links
+ links = auth_return.get('links', {})
+ link_list = links.get('link', [])
+ if isinstance(link_list, dict):
+ link_list = [link_list]
+
+ label_url = ''
+ for link in link_list:
+ if link.get('@rel') == 'labelDetails':
+ label_url = link.get('@href', '')
+ break
+
+ if label_url:
+ label_resp = request(
+ method='GET', url=label_url,
+ headers={
+ 'Accept': 'application/pdf',
+ 'Accept-language': 'en-CA',
+ },
+ auth=(carrier.username, carrier.password))
+ if label_resp.status_code == 200:
+ attachment = self.env['ir.attachment'].create({
+ 'name': 'Return-Label-%s.pdf' % tracking_pin,
+ 'type': 'binary',
+ 'datas': base64.b64encode(
+ label_resp.content).decode(),
+ 'res_model': self._name,
+ 'res_id': self.id,
+ 'mimetype': 'application/pdf',
+ })
+ self.return_label_attachment_id = attachment
+
+ self.message_post(
+ body=_("Return label created. Tracking: %s") % tracking_pin,
+ attachment_ids=(
+ self.return_label_attachment_id.ids
+ if self.return_label_attachment_id else []))
diff --git a/fusion_canada_post/models/sale_order.py b/fusion_canada_post/models/sale_order.py
index 36c32c2d..df51bd80 100644
--- a/fusion_canada_post/models/sale_order.py
+++ b/fusion_canada_post/models/sale_order.py
@@ -27,17 +27,20 @@ class SaleOrder(models.Model):
fusion_cp_package_height = fields.Float(
string='Package Height', copy=False)
+ fusion_cp_shipment_ids = fields.One2many(
+ 'fusion.cp.shipment',
+ 'sale_order_id',
+ string='CP Shipments',
+ )
fusion_cp_shipment_count = fields.Integer(
string='CP Shipments',
compute='_compute_fusion_cp_shipment_count',
)
def _compute_fusion_cp_shipment_count(self):
- Shipment = self.env['fusion.cp.shipment']
for order in self:
- order.fusion_cp_shipment_count = Shipment.search_count(
- [('sale_order_id', '=', order.id)]
- )
+ order.fusion_cp_shipment_count = len(
+ order.fusion_cp_shipment_ids)
def action_view_fusion_cp_shipments(self):
self.ensure_one()
diff --git a/fusion_canada_post/views/fusion_cp_shipment_views.xml b/fusion_canada_post/views/fusion_cp_shipment_views.xml
index aa941d0b..8a8f66c4 100644
--- a/fusion_canada_post/views/fusion_cp_shipment_views.xml
+++ b/fusion_canada_post/views/fusion_cp_shipment_views.xml
@@ -19,6 +19,8 @@
domain="[('status','=','shipped')]"/>
+
@@ -45,6 +47,7 @@
+
@@ -53,6 +56,7 @@
decoration-info="status == 'draft'"
decoration-success="status in ('confirmed','shipped')"
decoration-bf="status == 'delivered'"
+ decoration-warning="status == 'returned'"
decoration-danger="status == 'cancelled'"
widget="badge"/>
@@ -76,11 +80,17 @@
class="btn-secondary"
icon="fa-external-link"
invisible="not tracking_number"/>
+
+
@@ -155,6 +167,7 @@
+ Printable Label (4x6)
+ Return Label
+
Receipt
@@ -191,7 +210,7 @@
+ invisible="status not in ('delivered', 'returned')"/>
@@ -248,9 +267,9 @@
-
+
- Shipments
+ All Shipmentsfusion.cp.shipmentlist,form,kanban
@@ -263,4 +282,36 @@
+
+ Confirmed
+ fusion.cp.shipment
+ list,form,kanban
+ [('status', '=', 'confirmed')]
+
+
+
+
+ Shipped
+ fusion.cp.shipment
+ list,form,kanban
+ [('status', '=', 'shipped')]
+
+
+
+
+ Delivered
+ fusion.cp.shipment
+ list,form,kanban
+ [('status', '=', 'delivered')]
+
+
+
+
+ Returned
+ fusion.cp.shipment
+ list,form,kanban
+ [('status', '=', 'returned')]
+
+
+
diff --git a/fusion_canada_post/views/menus.xml b/fusion_canada_post/views/menus.xml
index 85622167..6f9fdc93 100644
--- a/fusion_canada_post/views/menus.xml
+++ b/fusion_canada_post/views/menus.xml
@@ -6,13 +6,43 @@
web_icon="fusion_canada_post,static/description/icon.png"
sequence="85"/>
-
+
+
+
+
+
+
+
+
+
+
+
+