# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Joël Grand-Guillaume, Guewen Baconnier
# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
import xmlrpclib
from openerp import models, fields, _
from openerp.addons.connector.queue.job import job, related_action
from openerp.addons.connector.unit.synchronizer import Exporter
from openerp.addons.connector.event import on_record_create
from openerp.addons.connector_ecommerce.event import (on_invoice_paid,
on_invoice_validated)
from openerp.addons.connector.exception import IDMissingInBackend
from .unit.backend_adapter import GenericAdapter
from .connector import get_environment
from .backend import magento
from .related_action import unwrap_binding
_logger = logging.getLogger(__name__)
[docs]class MagentoAccountInvoice(models.Model):
""" Binding Model for the Magento Invoice """
_name = 'magento.account.invoice'
_inherit = 'magento.binding'
_inherits = {'account.invoice': 'openerp_id'}
_description = 'Magento Invoice'
openerp_id = fields.Many2one(comodel_name='account.invoice',
string='Invoice',
required=True,
ondelete='cascade')
magento_order_id = fields.Many2one(comodel_name='magento.sale.order',
string='Magento Sale Order',
ondelete='set null')
_sql_constraints = [
('openerp_uniq', 'unique(backend_id, openerp_id)',
'A Magento binding for this invoice already exists.'),
]
[docs]class AccountInvoice(models.Model):
""" Adds the ``one2many`` relation to the Magento bindings
(``magento_bind_ids``)
"""
_inherit = 'account.invoice'
magento_bind_ids = fields.One2many(
comodel_name='magento.account.invoice',
inverse_name='openerp_id',
string='Magento Bindings',
)
[docs]@magento
class AccountInvoiceAdapter(GenericAdapter):
""" Backend Adapter for the Magento Invoice """
_model_name = 'magento.account.invoice'
_magento_model = 'sales_order_invoice'
_admin_path = 'sales_invoice/view/invoice_id/{id}'
def _call(self, method, arguments):
try:
return super(AccountInvoiceAdapter, self)._call(method, arguments)
except xmlrpclib.Fault as err:
# this is the error in the Magento API
# when the invoice does not exist
if err.faultCode == 100:
raise IDMissingInBackend
else:
raise
[docs] def create(self, order_increment_id, items, comment, email,
include_comment):
""" Create a record on the external system """
return self._call('%s.create' % self._magento_model,
[order_increment_id, items, comment,
email, include_comment])
[docs] def search_read(self, filters=None, order_id=None):
""" Search records according to some criterias
and returns their information
:param order_id: 'order_id' field of the magento sale order, this
is not the same field than 'increment_id'
"""
if filters is None:
filters = {}
if order_id is not None:
filters['order_id'] = {'eq': order_id}
return super(AccountInvoiceAdapter, self).search_read(filters=filters)
[docs]@magento
class MagentoInvoiceExporter(Exporter):
""" Export invoices to Magento """
_model_name = ['magento.account.invoice']
def _export_invoice(self, magento_id, lines_info, mail_notification):
if not lines_info: # invoice without any line for the sale order
return
return self.backend_adapter.create(magento_id,
lines_info,
_("Invoice Created"),
mail_notification,
False)
def _get_lines_info(self, invoice):
"""
Get the line to export to Magento. In case some lines doesn't have a
matching on Magento, we ignore them. This allow to add lines manually.
:param invoice: invoice is an magento.account.invoice record
:type invoice: browse_record
:return: dict of {magento_product_id: quantity}
:rtype: dict
"""
item_qty = {}
# get product and quantities to invoice
# if no magento id found, do not export it
order = invoice.magento_order_id
for line in invoice.invoice_line:
product = line.product_id
# find the order line with the same product
# and get the magento item_id (id of the line)
# to invoice
order_line = next((line for line in order.magento_order_line_ids
if line.product_id.id == product.id),
None)
if order_line is None:
continue
item_id = order_line.magento_id
item_qty.setdefault(item_id, 0)
item_qty[item_id] += line.quantity
return item_qty
[docs] def run(self, binding_id):
""" Run the job to export the validated/paid invoice """
invoice = self.model.browse(binding_id)
magento_order = invoice.magento_order_id
magento_store = magento_order.store_id
mail_notification = magento_store.send_invoice_paid_mail
lines_info = self._get_lines_info(invoice)
magento_id = None
try:
magento_id = self._export_invoice(magento_order.magento_id,
lines_info,
mail_notification)
except xmlrpclib.Fault as err:
# When the invoice is already created on Magento, it returns:
# <Fault 102: 'Cannot do invoice for order.'>
# We'll search the Magento invoice ID to store it in OpenERP
if err.faultCode == 102:
_logger.debug('Invoice already exists on Magento for '
'sale order with magento id %s, trying to find '
'the invoice id.',
magento_order.magento_id)
magento_id = self._get_existing_invoice(magento_order)
if magento_id is None:
# In that case, we let the exception bubble up so
# the user is informed of the 102 error.
# We couldn't find the invoice supposedly existing
# so an investigation may be necessary.
raise
else:
raise
# When the invoice already exists on Magento, it may return
# a 102 error (handled above) or return silently without ID
if not magento_id:
# If Magento returned no ID, try to find the Magento
# invoice, but if we don't find it, let consider the job
# as done, because Magento did not raised an error
magento_id = self._get_existing_invoice(magento_order)
if magento_id:
self.binder.bind(magento_id, binding_id)
def _get_existing_invoice(self, magento_order):
invoices = self.backend_adapter.search_read(
order_id=magento_order.magento_order_id)
if not invoices:
return
if len(invoices) > 1:
return
return invoices[0]['increment_id']
MagentoInvoiceSynchronizer = MagentoInvoiceExporter # deprecated
[docs]@on_invoice_validated
@on_invoice_paid
def invoice_create_bindings(session, model_name, record_id):
"""
Create a ``magento.account.invoice`` record. This record will then
be exported to Magento.
"""
invoice = session.env[model_name].browse(record_id)
# find the magento store to retrieve the backend
# we use the shop as many sale orders can be related to an invoice
for sale in invoice.sale_ids:
for magento_sale in sale.magento_bind_ids:
binding_exists = False
for mag_inv in invoice.magento_bind_ids:
if mag_inv.backend_id.id == magento_sale.backend_id.id:
binding_exists = True
break
if binding_exists:
continue
# Check if invoice state matches configuration setting
# for when to export an invoice
magento_store = magento_sale.store_id
payment_method = sale.payment_method_id
if payment_method and payment_method.create_invoice_on:
create_invoice = payment_method.create_invoice_on
else:
create_invoice = magento_store.create_invoice_on
if create_invoice == invoice.state:
session.env['magento.account.invoice'].create({
'backend_id': magento_sale.backend_id.id,
'openerp_id': invoice.id,
'magento_order_id': magento_sale.id})
[docs]@on_record_create(model_names='magento.account.invoice')
def delay_export_account_invoice(session, model_name, record_id, vals):
"""
Delay the job to export the magento invoice.
"""
export_invoice.delay(session, model_name, record_id)
[docs]@job(default_channel='root.magento')
@related_action(action=unwrap_binding)
def export_invoice_paid(session, model_name, record_id):
""" Deprecated in 2.1.0.dev0. """
_logger.warning('Deprecated: the export_invoice_paid() job is deprecated '
'in favor of export_invoice()')
return export_invoice(session, model_name, record_id)
[docs]@job(default_channel='root.magento')
@related_action(action=unwrap_binding)
def export_invoice(session, model_name, record_id):
""" Export a validated or paid invoice. """
invoice = session.env[model_name].browse(record_id)
backend_id = invoice.backend_id.id
env = get_environment(session, model_name, backend_id)
invoice_exporter = env.get_connector_unit(MagentoInvoiceExporter)
return invoice_exporter.run(record_id)