Ejemplo n.º 1
0
class Workcenter(Model):
    _name = "workcenter"
    _string = "Workcenter"
    _key = ["code"]
    _export_name_field = "code"
    _fields = {
        "code": fields.Char("Workcenter Code", search=True),
        "name": fields.Char("Workcenter Name", search=True),
        "location_id": fields.Many2One("stock.location", "Location"),
        "asset_id": fields.Many2One("account.fixed.asset", "Fixed Asset"),
        "hours_history": fields.Json("Hours History", function="get_hours_history"),
        "hours_week": fields.Decimal("Hours This Week", function="get_hours", function_multi=True),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "documents": fields.One2Many("document", "related_id", "Documents"),
    }
    _order = "code"

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.code:
                name = "%s [%s]" % (obj.name, obj.code)
            else:
                name = obj.name
            vals.append((obj.id, name))
        return vals

    def name_search(self, name, condition=None, context={}, limit=None, **kw):
        cond = [["code", "ilike", "%" + name + "%"]]
        if condition:
            cond = [cond, condition]
        ids1 = self.search(cond, limit=limit)
        cond = [["name", "ilike", "%" + name + "%"]]
        if condition:
            cond = [cond, condition]
        ids2 = self.search(cond, limit=limit)
        ids = list(set(ids1 + ids2))
        return self.name_get(ids, context=context)

    def get_hours_history(self, ids, context={}):
        db = get_connection()
        vals = {}
        days = get_days(30)
        for id in ids:
            res = db.query(
                "SELECT o.date,SUM(o.hours) AS hours FROM mrp_operation o WHERE workcenter_id=%s GROUP BY o.date", id)
            hours = {}
            for r in res:
                hours[r.date] = r.hours
            data = []
            for d in days:
                data.append((js_time(d), hours.get(d, 0)))
            vals[id] = data
        return vals

    def get_hours(self, ids, context={}):
        db = get_connection()
        d = date.today()
        date_from = d - timedelta(days=d.weekday())
        vals = {}
        for id in ids:
            res = db.get(
                "SELECT SUM(o.hours) AS hours FROM mrp_operation o WHERE o.workcenter_id=%s AND o.date>=%s", id, date_from)
            vals[id] = {
                "hours_week": res.hours or 0,
            }
        return vals
Ejemplo n.º 2
0
class Cart(Model):
    _name = "ecom2.cart"
    _string = "Cart"
    _name_field = "number"
    _audit_log = True
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "date":
        fields.DateTime("Date Created", required=True, search=True),
        "customer_id":
        fields.Many2One("contact", "Customer", search=True),
        "lines":
        fields.One2Many("ecom2.cart.line", "cart_id", "Lines"),
        "ship_amount_details":
        fields.Json("Shipping Amount Details",
                    function="get_ship_amount_details"),
        "amount_ship":
        fields.Decimal("Shipping Amount", function="get_amount_ship"),
        "amount_total":
        fields.Decimal("Total Amount", function="get_total"),
        "sale_orders":
        fields.One2Many("sale.order", "related_id", "Sales Orders"),
        "delivery_date":
        fields.Date("Delivery Date"),
        "ship_address_id":
        fields.Many2One("address", "Shipping Address"),
        "bill_address_id":
        fields.Many2One("address", "Billing Address"),
        "delivery_slot_id":
        fields.Many2One("delivery.slot", "Peferred Delivery Slot"),
        "ship_method_id":
        fields.Many2One("ship.method", "Shipping Method"),
        "pay_method_id":
        fields.Many2One("payment.method", "Payment Method"),
        "logs":
        fields.One2Many("log", "related_id", "Audit Log"),
        "state":
        fields.Selection([["draft", "Draft"], ["confirmed", "Confirmed"],
                          ["canceled", "Canceled"]],
                         "Status",
                         required=True),
        "payment_methods":
        fields.Json("Payment Methods", function="get_payment_methods"),
        "delivery_delay":
        fields.Integer("Delivery Delay (Days)", function="get_delivery_delay"),
        "delivery_slots":
        fields.Json("Delivery Slots", function="get_delivery_slots"),
        "delivery_slots_str":
        fields.Text("Delivery Slots", function="get_delivery_slots_str"),
        "date_delivery_slots":
        fields.Json("Date Delivery Slots", function="get_date_delivery_slots"),
        "comments":
        fields.Text("Comments"),
        "transaction_no":
        fields.Char("Payment Transaction No.", search=True),
        "currency_id":
        fields.Many2One("currency", "Currency", required=True),
        "invoices":
        fields.One2Many("account.invoice", "related_id", "Invoices"),
        "company_id":
        fields.Many2One("company", "Company"),
        "voucher_id":
        fields.Many2One("sale.voucher", "Voucher"),
        "ship_addresses":
        fields.Json("Shipping Addresses", function="get_ship_addresses"),
        "amount_voucher":
        fields.Decimal("Voucher Amount",
                       function="get_amount_voucher",
                       function_multi=True),
        "voucher_error_message":
        fields.Text("Voucher Error Message",
                    function="get_amount_voucher",
                    function_multi=True),
        "free_ship_address":
        fields.Boolean("Is free Ship"),
    }
    _order = "date desc"

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="ecom_cart")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            if not num:
                return None
            user_id = access.get_active_user()
            access.set_active_user(1)
            res = self.search([["number", "=", num]])
            access.set_active_user(user_id)
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    def _get_currency(self, context={}):
        res = get_model("company").search([])  # XXX
        if not res:
            return
        company_id = res[0]
        access.set_active_company(company_id)
        settings = get_model("settings").browse(1)
        return settings.currency_id.id

    def _get_company(self, context={}):
        res = get_model("company").search([])  # XXX
        if res:
            return res[0]

    def _get_ship_method(self, context={}):
        res = get_model("ship.method").search([], order="sequence")
        if res:
            return res[0]

    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "number": _get_number,
        "state": "draft",
        "currency_id": _get_currency,
        "company_id": _get_company,
        "ship_method_d": _get_ship_method,
        "free_ship_address": False,
    }

    def get_ship_amount_details(self, ids, context={}):
        print("get_ship_amount_details", ids)
        vals = {}
        for obj in self.browse(ids):
            delivs = []
            for line in obj.lines:
                date = line.delivery_date
                meth_id = line.ship_method_id.id or line.cart_id.ship_method_id.id
                addr_id = line.ship_address_id.id or line.cart_id.ship_address_id.id
                if not date or not meth_id or not addr_id:
                    continue
                delivs.append((date, meth_id, addr_id))
            delivs = list(set(delivs))
            details = []
            for date, meth_id, addr_id in delivs:
                ctx = {
                    "contact_id": obj.customer_id.id,
                    "ship_address_id": addr_id,
                }
                meth = get_model("ship.method").browse(meth_id, context=ctx)
                details.append({
                    "ship_addr_id": addr_id,
                    "date": date,
                    "ship_method_id": meth.id,
                    "ship_amount": meth.ship_amount,
                })
            vals[obj.id] = details
        return vals

    def get_amount_ship(self, ids, context={}):
        print("get_amount_ship", ids)
        vals = {}
        for obj in self.browse(ids):
            ship_amt = 0
            for d in obj.ship_amount_details:
                ship_amt += d["ship_amount"] or 0
            vals[obj.id] = ship_amt
        return vals

    def get_total(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = 0
            for line in obj.lines:
                amt += line.amount
            vals[obj.id] = amt + obj.amount_ship - obj.amount_voucher
        return vals

    def get_payment_methods(self, ids, context={}):
        res = []
        for obj in get_model("payment.method").search_browse([]):
            res.append({
                "id": obj.id,
                "name": obj.name,
            })
        return {ids[0]: res}

    def confirm(self, ids, context={}):
        obj = self.browse(ids[0])
        user_id = context.get("user_id")  # XX: remove this
        if user_id:
            user_id = int(user_id)
            user = get_model("base.user").browse(user_id)
            if user.contact_id:
                obj.write({"customer_id": user.contact_id.id})
        access.set_active_company(1)  # XXX
        vals = {
            "contact_id": obj.customer_id.id,
            "ship_address_id": obj.ship_address_id.id,
            "ship_method_id": obj.ship_method_id.id,
            "bill_address_id": obj.bill_address_id.id,
            "due_date": obj.delivery_date,
            "lines": [],
            "related_id": "ecom2.cart,%s" % obj.id,
            "delivery_slot_id": obj.delivery_slot_id.id,
            "pay_method_id": obj.pay_method_id.id,
            "other_info": obj.comments,
            "voucher_id": obj.voucher_id.id,
            "ref": obj.comments,  # XXX
        }
        print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ship Method Id ",
              ship_method_id)
        for line in obj.lines:
            prod = line.product_id
            if line.lot_id and line.qty_avail <= 0:
                raise Exception("Lot is out of stock (%s)" % prod.name)
            if not prod.locations:
                raise Exception("Can't find location for product %s" %
                                prod.code)
            location_id = prod.locations[0].location_id.id
            line_vals = {
                "product_id": prod.id,
                "description": prod.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "location_id": location_id,
                "lot_id": line.lot_id.id,
                "due_date": line.delivery_date,
                "delivery_slot_id": obj.delivery_slot_id.id,
                "ship_address_id": line.ship_address_id.id,
            }
            vals["lines"].append(("create", line_vals))
        for ship in obj.ship_amount_details:
            meth_id = ship["ship_method_id"]
            amount = ship["ship_amount"]
            meth = get_model("ship.method").browse(meth_id)
            prod = meth.product_id
            if not prod:
                raise Exception("Missing product in shipping method %s" %
                                meth.name)
            line_vals = {
                "product_id": prod.id,
                "description": prod.description,
                "qty": 1,
                "uom_id": prod.uom_id.id,
                "unit_price": amount,
            }
            vals["lines"].append(("create", line_vals))
        sale_id = get_model("sale.order").create(vals)
        sale = get_model("sale.order").browse(sale_id)
        sale.confirm()
        obj.write({"state": "confirmed"})
        obj.trigger("confirm_order")
        return {
            "sale_id": sale_id,
        }

    def cancel_order(self, ids, context={}):
        obj = self.browse(ids[0])
        for sale in obj.sale_orders:
            if sale.state == "voided":
                continue
            for inv in sale.invoices:
                if inv.state != "voided":
                    raise Exception(
                        "Can not cancel order %s because there are linked invoices"
                        % sale.number)
            for pick in sale.pickings:
                if pick.state == "voided":
                    continue
                pick.void()
            sale.void()
        for inv in obj.invoices:
            if inv.state != "voided":
                raise Exception(
                    "Can not cancel cart %s because there are linked invoices"
                    % obj.number)
        obj.write({"state": "canceled"})

    def payment_received(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.state != "waiting_payment":
            raise Exception("Cart is not waiting payment")
        res = obj.sale_orders.copy_to_invoice()
        inv_id = res["invoice_id"]
        inv = get_model("account.invoice").browse(inv_id)
        inv.write({"related_id": "ecom2.cart,%s" % obj.id})
        inv.post()
        method = obj.pay_method_id
        if not method:
            raise Exception("Missing payment method in cart %s" % obj.number)
        if not method.account_id:
            raise Exception("Missing account in payment method %s" %
                            method.name)
        transaction_no = context.get("transaction_no")
        pmt_vals = {
            "type": "in",
            "pay_type": "invoice",
            "contact_id": inv.contact_id.id,
            "account_id": method.account_id.id,
            "lines": [],
            "company_id": inv.company_id.id,
            "transaction_no": transaction_no,
        }
        line_vals = {
            "invoice_id": inv_id,
            "amount": inv.amount_total,
        }
        pmt_vals["lines"].append(("create", line_vals))
        pmt_id = get_model("account.payment").create(pmt_vals,
                                                     context={"type": "in"})
        get_model("account.payment").post([pmt_id])
        obj.write({"state": "paid"})
        for sale in obj.sale_orders:
            for pick in sale.pickings:
                if pick.state == "pending":
                    pick.approve()

    def to_draft(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.write({"state": "draft"})

    def set_qty(self, ids, prod_id, qty, context={}):
        print("Cart.set_qty", ids, prod_id, qty)
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.product_id.id == prod_id and not line.lot_id:
                line_id = line.id
                break
        if line_id:
            if qty == 0:
                get_model("ecom2.cart.line").delete([line_id])
            else:
                get_model("ecom2.cart.line").write([line_id], {"qty": qty})
        else:
            if qty != 0:
                get_model("ecom2.cart.line").create({
                    "cart_id": obj.id,
                    "product_id": prod_id,
                    "qty": qty
                })

    def set_date_qty(self, ids, due_date, prod_id, qty, context={}):
        print("Cart.set_date_qty", ids, due_date, prod_id, qty)
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.delivery_date == due_date and line.product_id.id == prod_id and not line.lot_id:
                line_id = line.id
                break
        if line_id:
            if qty == 0:
                get_model("ecom2.cart.line").delete([line_id])
            else:
                get_model("ecom2.cart.line").write([line_id], {"qty": qty})
        else:
            if qty != 0:
                ctx = {
                    "cart_id": obj.id,
                    "delivery_date": due_date,
                    "product_id": prod_id
                }
                get_model("ecom2.cart.line").create(
                    {
                        "cart_id": obj.id,
                        "product_id": prod_id,
                        "qty": qty,
                        "delivery_date": due_date
                    },
                    context=ctx)

    def add_lot(self, ids, prod_id, lot_id, context={}):
        print("Cart.add_lot", ids, prod_id, lot_id)
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.product_id.id == prod_id and line.lot_id.id == lot_id:
                line_id = line.id
                break
        if line_id:
            raise Exception("Lot already added to cart")
        get_model("ecom2.cart.line").create({
            "cart_id": obj.id,
            "product_id": prod_id,
            "lot_id": lot_id,
            "qty": 1
        })

    def remove_lot(self, ids, prod_id, lot_id, context={}):
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.product_id.id == prod_id and line.lot_id.id == lot_id:
                line_id = line.id
                break
        if not line_id:
            raise Exception("Lot not found in cart")
        get_model("ecom2.cart.line").delete([line_id])

    def set_qty_auto_select_lot(self, ids, prod_id, qty, context={}):
        print("Cart.set_qty_auto_select_lot", ids, prod_id, qty)
        obj = self.browse(ids[0])
        cur_qty = 0
        for line in obj.lines:
            if line.product_id.id == prod_id:
                cur_qty += line.qty
        diff_qty = qty - cur_qty
        if diff_qty > 0:
            self.add_lots_auto_select(ids, prod_id, diff_qty, context=context)
        elif diff_qty < 0:
            self.remove_lots_auto_select(ids,
                                         prod_id,
                                         -diff_qty,
                                         context=context)

    def add_lots_auto_select(self, ids, prod_id, add_qty, context={}):
        print("Cart.add_lots_auto_select", ids, prod_id, add_qty)
        obj = self.browse(ids[0])
        exclude_lot_ids = []
        for line in obj.lines:
            if line.product_id.id == prod_id and line.lot_id:
                exclude_lot_ids.append(line.lot_id.id)
        prod = get_model("product").browse(prod_id)
        avail_qty = prod.stock_qty
        add_lot_ids = []
        for lot in prod.stock_lots:
            if len(add_lot_ids) >= avail_qty:
                break
            if lot.id not in exclude_lot_ids:
                add_lot_ids.append(lot.id)
                if len(add_lot_ids) >= add_qty:
                    break
        print("add_lot_ids", add_lot_ids)
        for lot_id in add_lot_ids:
            get_model("ecom2.cart.line").create({
                "cart_id": obj.id,
                "product_id": prod_id,
                "lot_id": lot_id,
                "qty": 1
            })
        remain_qty = add_qty - len(add_lot_ids)
        print("remain_qty", remain_qty)
        if remain_qty > 0:
            found_line = None
            for line in obj.lines:
                if line.product_id.id == prod_id and not line.lot_id:
                    found_line = line
                    break
            if found_line:
                found_line.write({"qty": found_line.qty + remain_qty})
            else:
                get_model("ecom2.cart.line").create({
                    "cart_id": obj.id,
                    "product_id": prod_id,
                    "qty": remain_qty
                })

    def remove_lots_auto_select(self, ids, prod_id, remove_qty, context={}):
        print("Cart.remove_lots_auto_select", ids, prod_id, remove_qty)
        obj = self.browse(ids[0])
        remain_qty = remove_qty
        for line in obj.lines:
            if line.product_id.id == prod_id and not line.lot_id:
                if line.qty <= remain_qty:
                    remain_qty -= line.qty
                    line.delete()
                else:
                    line.write({"qty": line.qty - remain_qty})
                    remain_qty = 0
        print("remain_qty", remain_qty)
        if remain_qty > 0:
            lots = []
            for line in obj.lines:
                if line.product_id.id == prod_id and line.lot_id:
                    d = line.lot_id.received_date or "1900-01-01"
                    lots.append((d, line.id))
            lots.sort()
            del_ids = []
            for d, line_id in lots:
                del_ids.append(line_id)
                if len(del_ids) >= remain_qty:
                    break
            print("del_ids", del_ids)
            get_model("ecom2.cart.line").delete(del_ids)

    def get_delivery_delay(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = max(l.delivery_delay
                               for l in obj.lines) if obj.lines else 0
        return vals

    def get_delivery_slots(self, ids, context={}):
        print("get_delivery_slots", ids)
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        max_days = settings.delivery_max_days
        if not max_days:
            return {obj.id: []}
        min_hours = settings.delivery_min_hours or 0
        d_from = date.today() + timedelta(days=obj.delivery_delay)
        d_to = d_from + timedelta(days=max_days)
        d = d_from
        slots = []
        for slot in get_model("delivery.slot").search_browse([]):
            slots.append([slot.id, slot.name, slot.time_from])
        slot_num_sales = {}
        for sale in get_model("sale.order").search_browse(
            [["plr_order_type", "=", "grocery"],
             ["date", ">=", time.strftime("%Y-%m-%d")]]):
            k = (sale.due_date, sale.delivery_slot_id.id)
            slot_num_sales.setdefault(k, 0)
            slot_num_sales[k] += 1
        print("slot_num_sales", slot_num_sales)
        slot_caps = {}
        for cap in get_model("delivery.slot.capacity").search_browse([]):
            k = (cap.slot_id.id, int(cap.weekday))
            slot_caps[k] = cap.capacity
        print("slot_caps", slot_caps)
        delivery_weekdays = None
        for line in obj.lines:
            prod = line.product_id
            if prod.delivery_weekdays:
                days = [int(w) for w in prod.delivery_weekdays.split(",")]
                if delivery_weekdays == None:
                    delivery_weekdays = days
                else:
                    delivery_weekdays = [
                        d for d in delivery_weekdays if d in days
                    ]
        days = []
        now = datetime.now()
        tomorrow = now + timedelta(days=1)
        tomorrow_seconds = 0
        if settings.work_time_start and settings.work_time_end:
            today_end = datetime.strptime(
                date.today().strftime("%Y-%m-%d") + " " +
                settings.work_time_end, "%Y-%m-%d %H:%M")
            tomorrow_start = datetime.strptime(
                tomorrow.strftime("%Y-%m-%d") + " " + settings.work_time_start,
                "%Y-%m-%d %H:%M")
            if now < today_end and now.weekday() != 6:
                remain_seconds = (today_end - now).total_seconds()
            else:
                remain_seconds = 0
            if remain_seconds < min_hours * 3600:
                tomorrow_seconds = min_hours * 3600 - remain_seconds
        print("tomorrow_seconds=%s" % tomorrow_seconds)
        while d <= d_to:
            ds = d.strftime("%Y-%m-%d")
            res = get_model("hr.holiday").search([["date", "=", ds]])
            if res:
                d += timedelta(days=1)
                continue
            w = d.weekday()
            if w == 6 or delivery_weekdays is not None and w not in delivery_weekdays:
                d += timedelta(days=1)
                continue
            day_slots = []
            for slot_id, slot_name, from_time in slots:
                t_from = datetime.strptime(ds + " " + from_time + ":00",
                                           "%Y-%m-%d %H:%M:%S")
                capacity = slot_caps.get((slot_id, w))
                num_sales = slot_num_sales.get((ds, slot_id), 0)
                state = "avail"
                if d == now.date():
                    if t_from < now or (t_from - now).total_seconds(
                    ) < min_hours * 3600 or tomorrow_seconds:
                        state = "full"
                elif d == tomorrow.date() and tomorrow_seconds:
                    if (t_from -
                            tomorrow_start).total_seconds() < tomorrow_seconds:
                        state = "full"
                if capacity is not None and num_sales >= capacity:
                    state = "full"
                day_slots.append(
                    [slot_id, slot_name, state, num_sales, capacity])
            days.append([ds, day_slots])
            d += timedelta(days=1)
        print("days:")
        pprint(days)
        return {obj.id: days}

    def get_delivery_slots_str(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            s = ""
            for d, slots in obj.delivery_slots:
                s += "- Date: %s\n" % d
                for slot_id, name, state, num_sales, capacity in slots:
                    s += "    - %s: %s (%s/%s)\n" % (name, state, num_sales,
                                                     capacity or "-")
            vals[obj.id] = s
        return vals

    def get_date_delivery_slots(self, ids, context={}):
        print("get_date_delivery_slots", ids)
        obj = self.browse(ids[0])
        slots = []
        for slot in get_model("delivery.slot").search_browse([]):
            slots.append([slot.id, slot.name])
        dates = []
        for line in obj.lines:
            d = line.delivery_date
            if d:
                dates.append(d)
        dates = list(set(dates))
        date_slots = {}
        for d in dates:
            date_slots[d] = slots  # TODO: use capacity?
        return {obj.id: date_slots}

    def get_ship_addresses(self, ids, context={}):
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        contact = obj.customer_id
        addrs = []
        if contact:
            for a in contact.addresses:
                addr_vals = {
                    "id": a.id,
                    "name": a.address,
                }
                if obj.ship_method_id:  # TODO: handle general case for different shipping methods per order
                    meth_id = obj.ship_method_id.id
                    ctx = {"ship_address_id": a.id}
                    meth = get_model("ship.method").browse(meth_id,
                                                           context=ctx)
                    addr_vals["ship_amount"] = meth.ship_amount
                else:
                    addr_vals["ship_amount"] = 0
                addrs.append(addr_vals)
        for a in settings.extra_ship_addresses:
            addr_vals = {
                "id": a.id,
                "name": a.address or "",
            }
            if obj.ship_method_id:
                meth_id = obj.ship_method_id.id
                ctx = {"ship_address_id": a.id}
                meth = get_model("ship.method").browse(meth_id, context=ctx)
                addr_vals["ship_amount"] = meth.ship_amount
            else:
                addr_vals["ship_amount"] = 0
            addrs.append(addr_vals)
        return {obj.id: addrs}

    def apply_voucher_code(self, ids, voucher_code, context={}):
        obj = self.browse(ids[0])
        res = get_model("sale.voucher").search([["code", "=", voucher_code]])
        if not res:
            raise Exception("Invalid voucher code")
        voucher_id = res[0]
        obj.write({"voucher_id": voucher_id})

    def clear_voucher(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.write({"voucher_id": None})

    def get_amount_voucher(self, ids, context={}):
        print("get_amount_voucher", ids)
        vals = {}
        for obj in self.browse(ids):
            voucher = obj.voucher_id
            if voucher:
                ctx = {
                    "contact_id": obj.customer_id.id,
                    "amount_total": 0,
                    "products": [],
                }
                for line in obj.lines:
                    ctx["amount_total"] += line.amount
                    ctx["products"].append({
                        "product_id": line.product_id.id,
                        "unit_price": line.unit_price,
                        "qty": line.qty,
                        "uom_id": line.uom_id.id,
                        "amount": line.amount,
                    })
                ctx["amount_total"] += obj.amount_ship
                res = voucher.apply_voucher(context=ctx)
                disc_amount = res.get("discount_amount", 0)
                error_message = res.get("error_message")
            else:
                disc_amount = 0
                error_message = None
            vals[obj.id] = {
                "amount_voucher": disc_amount,
                "voucher_error_message": error_message,
            }
        return vals

    def update_date_delivery(self, ids, date, vals, context={}):
        print("cart.update_date_delivery", ids, date, vals)
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        for line in obj.lines:
            if line.delivery_date == date:
                line.write(vals)
        #for a in settings.extra_ship_addresses:
        #if a.id == vals['ship_address_id']:
        #return {'free_ship':True}
        return {'free_ship': False}

    def empty_cart(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.write({"lines": [("delete_all", )]})

    def check_due_dates(self, ids, context={}):
        obj = self.browse(ids[0])
        today = time.strftime("%Y-%m-%d")
        if obj.delivery_date and obj.delivery_date < today:
            raise Exception("Delivery date is in the past %s" %
                            obj.delivery_date)
        for line in obj.lines:
            if line.delivery_date and line.delivery_date < today:
                raise Exception(
                    "Delivery date is in the past %s for product %s" %
                    (line.delivery_date, line.product_id.name))

    def free_ship_address(self, ids, context={}):
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        data = []
        for a in settings.extra_ship_addresses:
            if obj.ship_method_id:
                data["is_free"] = False
            else:
                data["is_free"] = True
            data.append(data)
        return {obj.id: addrs}
Ejemplo n.º 3
0
class SplitProduction(Model):
    _name = "split.production"
    _transient = True
    _fields = {
        "order_id":
        fields.Many2One("production.order", "Production Order", required=True),
        "order_to_id":
        fields.Many2One("production.order",
                        "To Production Order",
                        required=True),
        "product_list":
        fields.Json("Product List"),
        "order_to_list":
        fields.Json("Production To List"),
        "product_id":
        fields.Many2One("product", "Product"),
        "planned_qty":
        fields.Decimal("Planned Qty", readonly=True),
        "actual_qty":
        fields.Decimal("Actual Qty", readonly=True),
        "split_qty":
        fields.Decimal("Split Qty"),
        "split_qty2":
        fields.Decimal("Split Secondary Qty"),
        "team_id":
        fields.Many2One("mfg.team", "Production Team"),
        "remark":
        fields.Char("Remark"),
        "ratio_method":
        fields.Selection(
            [["planned", "Planned Qty"], ["actual", "Actual Qty"]],
            "Ratio Method",
            required=True),
        "journal_id":
        fields.Many2One("stock.journal",
                        "Journal",
                        required=True,
                        condition=[["type", "=", "internal"]]),
        "container_id":
        fields.Many2One("stock.container", "Container"),
        "lines":
        fields.One2Many("split.production.line", "wizard_id", "Lines"),
        "remain_planned_qty":
        fields.Decimal("Remain Planned Qty",
                       function="get_remain_planned_qty"),
        "remain_actual_qty":
        fields.Decimal("Remain Actual Qty", function="get_remain_actual_qty"),
        "approved_by_id":
        fields.Many2One("base.user", "Approved By", readonly=True),
    }

    def _get_planned_qty(self, context={}):
        order_id = int(context["refer_id"])
        order = get_model("production.order").browse(order_id)
        return order.qty_planned

    def _get_actual_qty(self, context={}):
        order_id = int(context["refer_id"])
        order = get_model("production.order").browse(order_id)
        return order.qty_received

    def _get_product(self, context={}):
        order_id = int(context["refer_id"])
        order = get_model("production.order").browse(order_id)
        return order.product_id.id

    def _get_container_id(self, context={}):
        order_id = int(context["refer_id"])
        order = get_model("production.order").browse(order_id)
        return order.container_id.id

    def _get_product_ids(self, context={}):
        order_id = int(context["refer_id"])
        order = get_model("production.order").browse(order_id)
        prods = []
        for comp in order.components:
            prods.append(comp.product_id.id)
        prods.append(order.product_id.id)
        return prods

    def _get_order_to_ids(self, context={}):
        order_id = int(context["refer_id"])
        order = get_model("production.order").browse(order_id)
        order_to_ids = self.get_order_to_list(order.id)
        return order_to_ids

    _defaults = {
        "order_id": lambda self, ctx: int(ctx["refer_id"]),
        "planned_qty": _get_planned_qty,
        "actual_qty": _get_actual_qty,
        "product_id": _get_product,
        "product_list": _get_product_ids,
        "order_to_list": _get_order_to_ids,
        #"split_parents": True,
        "split_qty": 0,
        "container_id": _get_container_id,
        "ratio_method": "actual",
        "remain_planned_qty": _get_planned_qty,
        "remain_actual_qty": _get_actual_qty
    }

    def get_product_list(self, order_id):
        prods = []
        order = get_model("production.order").browse(order_id)
        if order:
            for comp in order.components:
                prods.append(comp.product_id.id)
            prods.append(order.product_id.id)
        return prods

    def get_product_ids(self, ids, context={}):
        res = {}
        prods = []
        obj = self.browse(ids)[0]
        order = obj.order_id
        if order:
            for comp in order.components:
                prods.append(comp.product_id.id)
            prods.append(order.product_id.id)
        res[obj.id] = prods
        return res

    def get_order_to_list(self, order_id):
        order_to_ids = [order_id]
        order = get_model("production.order").browse(order_id)
        order_parent = order.parent_id
        while order_parent:
            order_to_ids.append(order_parent.id)
            order_parent = order_parent.parent_id
        order_to_ids = list(set(order_to_ids))
        return order_to_ids

    def get_order_to_ids(self, ids, context={}):
        res = {}
        obj = self.browse(ids)[0]
        order_id = obj.order_id.id
        res[obj.id] = self.get_order_to_list(order_id)
        return res

    def get_remain_actual_qty(self, ids, context={}):
        res = {}
        obj = self.browse(ids)[0]
        if obj.ratio_method == "actual":
            total_qty = 0
            for line in obj.lines:
                total_qty += line.qty
            res[obj.id] = obj.actual_qty - total_qty
        else:
            res[obj.id] = obj.actual_qty
        return res

    def get_remain_planned_qty(self, ids, context={}):
        res = {}
        obj = self.browse(ids)[0]
        if obj.ratio_method == "planned":
            total_qty = 0
            for line in obj.lines:
                total_qty += line.qty
            res[obj.id] = obj.planned_qty - total_qty
        else:
            res[obj.id] = obj.planned_qty
        return res

    def onchange_order(self, context={}):
        data = context["data"]
        order_id = data["order_id"]
        order = get_model("production.order").browse(order_id)
        data["product_list"] = self.get_product_list(order_id)
        data["product_id"] = order.product_id.id
        data["order_to_list"] = self.get_order_to_list(order_id)
        data["order_to_id"] = None
        self.onchange_product(context)
        return data

    def get_split_num(self, root_num, context={}):
        root_num = re.sub("-P[0-9][0-9]$", "", root_num)
        for i in range(2, 100):
            num = root_num + "-P%.2d" % i
            res = get_model("production.order").search([["number", "=", num]])
            if not res:
                return num
        raise Exception(
            "Failed to generate production order number (root=%s)" % root_num)

    def get_split_container(self, prev_cont_num, order_num, context={}):
        part_no = order_num.rpartition("-")[2]
        if not part_no or not part_no.startswith("P") or not len(part_no) == 3:
            raise Exception(
                "Can not find split part number of production order %s" %
                order_num)
        new_cont_num = prev_cont_num + "-" + part_no
        res = get_model("stock.container").search(
            [["number", "=", new_cont_num]])
        if res:
            new_cont_id = res[0]
        else:
            vals = {
                "number": new_cont_num,
            }
            new_cont_id = get_model("stock.container").create(vals)
        return new_cont_id

    def check_split_container(self, order_comp_id):
        return True

    def get_lot(self, new_lot_num, context={}):
        res = get_model("stock.lot").search([["number", "=", new_lot_num]])
        if res:
            new_lot_id = res[0]
        else:
            vals = {
                "number": new_lot_num,
            }
            new_lot_id = get_model("stock.lot").create(vals)
        return new_lot_id

    def copy_order(self, order_id, qty, team_id, remark):
        order = get_model("production.order").browse(order_id)
        old_order_num = order.number
        new_order_num = self.get_split_num(old_order_num)
        vals = {
            "number": new_order_num,
            "order_date": time.strftime("%Y-%m-%d"),
            "due_date": order.due_date,
            "ref": order.ref,
            "sale_id": order.sale_id.id,
            "parent_id": order.parent_id.id,
            "product_id": order.product_id.id,
            "qty_planned": qty,
            "uom_id": order.uom_id.id,
            "bom_id": order.bom_id.id,
            "routing_id": order.routing_id.id,
            "production_location_id": order.production_location_id.id,
            "location_id": order.location_id.id,
            "team_id": team_id,
            "remark": remark,
            "state": order.state,
            "components": [],
            "operations": [],
            "qc_tests": [],
        }
        if order.container_id:
            vals["container_id"] = self.get_split_container(
                order.container_id.number, new_order_num)
        if order.lot_id and order.lot_id.number == old_order_num:  # XXX
            vals["lot_id"] = self.get_lot(new_order_num)
        ratio = qty / order.qty_planned
        for comp in order.components:
            comp_vals = {
                "product_id": comp.product_id.id,
                "qty_planned": round(comp.qty_planned * ratio, 2),
                "uom_id": comp.uom_id.id,
                "location_id": comp.location_id.id,
                "issue_method": comp.issue_method,
                "container_id": comp.container_id.id,
            }
            if comp.container_id and self.check_split_container(
                    comp.id):  # MTS need no need to split scrap box
                comp_vals["container_id"] = self.get_split_container(
                    comp.container_id.number, new_order_num)
            # if comp.lot_id and comp.lot_id.number==old_order_num: # XXX
            # comp_vals["lot_id"]=self.get_lot(new_order_num)
            comp_vals["lot_id"] = comp.lot_id.id  # Should be old number
            vals["components"].append(("create", comp_vals))
        for op in order.operations:
            op_vals = {
                "workcenter_id": op.workcenter_id.id,
                "employee_id": op.employee_id.id,
                "planned_duration": op.planned_duration * ratio,
            }
            vals["operations"].append(("create", op_vals))
        for qc in order.qc_tests:
            qc_vals = {
                "test_id": qc.test_id.id,
            }
            vals["qc_tests"].append(("create", qc_vals))
        new_id = get_model("production.order").create(vals)
        return new_id

    def modif_order(self, order_id, qty, team_id, remark):
        order = get_model("production.order").browse(order_id)
        ratio = qty / order.qty_planned
        old_order_num = order.number
        new_order_num = old_order_num + "-P01"
        vals = {
            "number": new_order_num,
            "qty_planned": round(order.qty_planned * ratio, 2),
            "team_id": team_id,
            "remark": remark,
        }
        if order.container_id:
            vals["container_id"] = self.get_split_container(
                order.container_id.number, new_order_num)
        if order.lot_id and order.lot_id.number == old_order_num:  # XXX
            vals["lot_id"] = self.get_lot(new_order_num)
        order.write(vals)
        for comp in order.components:
            vals = {
                "qty_planned": round(comp.qty_planned * ratio, 2),
            }
            if comp.container_id and self.check_split_container(
                    comp.id):  # MTS no need to split scrap box
                vals["container_id"] = self.get_split_container(
                    comp.container_id.number, new_order_num)
            # if comp.lot_id and comp.lot_id.number==old_order_num: # XXX
            # vals["lot_id"]=self.get_lot(new_order_num)
            vals["lot_id"] = comp.lot_id.id  # Should be old number
            comp.write(vals)
        for op in order.operations:
            vals = {
                "planned_duration": op.planned_duration * ratio,
            }
            op.write(vals)

    def split_order(self, order_id, ratios):
        order = get_model("production.order").browse(order_id)
        if order.state not in ("draft", "waiting_confirm", "waiting_material",
                               "waiting_suborder", "ready", "in_progress"):
            raise Exception("Invalid state to split order (%s)" % order.number)
        for r in ratios[:1]:
            split_ids = [(r[2], order_id)]
        for r in ratios[1:]:
            split_qty = order.qty_planned * r[0]
            team_id = r[1]
            remark = r[3]
            split_id = self.copy_order(order.id, split_qty, team_id, remark)
            split_ids.append((r[2], split_id))
        r = ratios[0]
        split_qty = order.qty_planned * r[0]
        team_id = r[1]
        remark = r[3]
        self.modif_order(order.id, split_qty, team_id, remark)
        for sub in order.sub_orders:
            if sub.state not in ("draft", "waiting_confirm",
                                 "waiting_material", "waiting_suborder",
                                 "ready", "in_progress"):
                continue
            sub_split_ids = self.split_order(sub.id, ratios)
            if sub.sub_orders:
                split_ids += sub_split_ids
            for i in range(len(sub_split_ids)):
                sub_split_id = sub_split_ids[i][1]
                split_id = split_ids[i][1]
                get_model("production.order").write([sub_split_id],
                                                    {"parent_id": split_id})
        return split_ids

    def do_split(self, ids, context={}):
        obj = self.browse(ids)[0]
        if not obj.approved_by_id:
            raise Exception("Split order has to be approved first")
        order = obj.order_id
        if len(obj.lines) < 2:
            raise Exception("Split needs at least 2 lines")
        total_qty = sum(l.qty for l in obj.lines)
        if not obj.ratio_method:
            raise Exception("Please select ratio method")
        if obj.ratio_method == "planned" and abs(total_qty -
                                                 obj.planned_qty) > 0.01:
            raise Exception("Total split qty has to be equal to planned qty")
        if obj.ratio_method == "actual" and abs(total_qty -
                                                obj.actual_qty) > 0.01:
            raise Exception("Total split qty has to be equal to actual qty")
        ratios = []
        if obj.ratio_method == "planned":
            for line in obj.lines:
                ratios.append((line.qty / obj.planned_qty, line.team_id.id,
                               line.id, line.remark))
        elif obj.ratio_method == "actual":
            for line in obj.lines:
                ratios.append((line.qty / obj.actual_qty, line.team_id.id,
                               line.id, line.remark))
        split_order = order
        if obj.order_to_id:
            # if obj.split_parents:
            while split_order.parent_id:
                split_order = split_order.parent_id
                if split_order.id == obj.order_to_id.id:
                    break
        split_order_ids = self.split_order(split_order.id, ratios)

        # Combine Split Order
        end_order = obj.order_id.parent_id
        if obj.order_to_id and obj.order_to_id.parent_id:
            end_order = obj.order_to_id.parent_id
        if end_order:
            comps = []
            for end_sub in end_order.sub_orders:
                for comp in end_order.components:
                    if comp.product_id.id == end_sub.product_id.id:
                        comps.append((comp.product_id.id, comp.location_id.id,
                                      comp.issued_method))
                        comp.delete()
            comps = list(set(comps))
            for prod_id, loc_id, issued_method in comps:
                for end_sub in end_order.sub_orders:
                    if end_sub.product_id.id == prod_id:
                        vals = {
                            "order_id": end_order.id,
                            "product_id": end_sub.product_id.id,
                            "qty_planned": end_sub.qty_planned,
                            "uom_id": end_sub.uom_id.id,
                            "location_id": loc_id,
                            "issue_method": issued_method,
                            "lot_id": end_sub.lot_id.id,
                            "container_id": end_sub.container_id.id,
                        }
                        get_model("production.component").create(vals)

        if obj.ratio_method == "actual":
            self.split_transfer(split_order_ids=split_order_ids,
                                split_prod_id=obj.id)
        return {
            "next": {
                "name": "production",
            },
            "flash": "Order split successfully",
        }

    def split_transfer(self, split_order_ids, split_prod_id):
        split_prod = get_model("split.production").browse(split_prod_id)
        pick_vals = {
            "type": "internal",
            "journal_id": split_prod.journal_id.id,
            "lines": [],
            "done_approved_by_id": split_prod.approved_by_id.id
        }
        for split_line, split_order_id in split_order_ids:
            split_order = get_model("production.order").browse(split_order_id)
            for line in split_prod.lines:
                cont_to_id = None
                lot_id = None
                if line.id == split_line:
                    if split_prod.product_id.id == split_order.product_id.id:
                        lot_id = split_order.lot_id.id
                        cont_to_id = split_order.container_id.id
                    else:
                        for comp in split_order.components:
                            if split_prod.product_id.id == comp.product_id.id:
                                lot_id = comp.lot_id.id
                                cont_to_id = comp.container_id.id
                if cont_to_id:
                    break
            if cont_to_id:
                move_vals = {
                    "product_id": split_prod.product_id.id,
                    "qty": line.qty,
                    "uom_id": split_prod.product_id.uom_id.id,
                    "qty2": line.qty2,
                    "lot_id": lot_id,
                    "location_from_id": split_prod.order_id.location_id.id,
                    "location_to_id": split_prod.order_id.location_id.id,
                    "container_from_id": split_prod.container_id.id,
                    "container_to_id": cont_to_id,
                }
                pick_vals["lines"].append(("create", move_vals))
        if len(pick_vals["lines"]) > 0:
            pick_id = get_model("stock.picking").create(pick_vals,
                                                        context=pick_vals)
            get_model("stock.picking").set_done([pick_id])
            split_order_ids.reverse()
        for order_id in split_order_ids:
            order = get_model("production.order").browse(order_id[1])
            if order.parent_id:
                order.parent_id.update_status()

    def approve(self, ids, context={}):
        if not check_permission_other("production_approve_split"):
            raise Exception("Permission denied")
        obj = self.browse(ids)[0]
        user_id = get_active_user()
        obj.write({"approved_by_id": user_id})
        return {
            "next": {
                "name": "split_production",
                "active_id": obj.id,
            },
            "flash": "Split order approved successfully",
        }

    def onchange_product(self, context={}):
        data = context["data"]
        order_id = data["order_id"]
        order = get_model("production.order").browse(order_id)
        prod_id = data["product_id"]
        data["planned_qty"] = 0
        data["actual_qty"] = 0
        if order.product_id.id == prod_id:
            data["planned_qty"] = order.qty_planned
            data["actual_qty"] = order.qty_received
            data["container_id"] = order.container_id.id
        else:
            for comp in order.components:
                if comp.product_id.id == prod_id:
                    data["planned_qty"] = comp.qty_planned
                    data["actual_qty"] = comp.qty_stock
                    data["container_id"] = comp.container_id.id
        data["remain_planned_qty"] = data["planned_qty"]
        data["remain_actual_qty"] = data["actual_qty"]
        return data

    def add_lines(self, ids, context={}):
        obj = self.browse(ids)[0]
        if not obj.ratio_method:
            raise Exception("Invalid Ratio Method")
        remain = obj.remain_planned_qty if obj.ratio_method == "planned" else obj.remain_actual_qty
        total_qty = 0
        for line in obj.lines:
            if line.product_id.id != obj.product_id.id \
                    or line.ratio_method != obj.ratio_method:
                line.delete()
        for line in obj.lines:
            total_qty += line.qty
        if obj.split_qty != 0 and remain + 0.001 >= obj.split_qty:
            # part_no=len(obj.lines)+1
            # cont_num=obj.container_id.number+"-P%.2d"%part_no
            vals = {
                "wizard_id": obj.id,
                "ratio_method": obj.ratio_method,
                "product_id": obj.product_id.id,
                "qty": obj.split_qty,
                "qty2": obj.split_qty2,
                "team_id": obj.team_id.id,
                "remark": obj.remark,
                #"container_num": cont_num,
            }
            get_model("split.production.line").create(vals)
            # part_no=1
            # for line in obj.lines:
            # cont_num=obj.container_id.number+"-P%.2d"%part_no
            #line.write({"container_num": cont_num})
            # part_no+=1
            obj.split_qty = 0
            obj.team_id = None
        else:
            raise Exception("Split Qty is too high!")
        return {"flash": "Add line success", "focus_field": "split_qty"}

    def clear_lines(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.write({"lines": [("delete_all", )]})
        return {"flash": "Clear all split lines", "focus_field": "split_qty"}
Ejemplo n.º 4
0
class Location(Model):
    _name = "stock.location"
    _name_field = "name"
    #_key = ["code"]
    _string = "Location"
    _fields = {
        "name":
        fields.Char("Location Name", required=True, search=True, size=256),
        "code":
        fields.Char("Location Code", search=True),
        "type":
        fields.Selection(
            [["internal", "Internal"], ["customer", "Customers"],
             ["supplier", "Suppliers"], ["inventory", "Inventory Loss"],
             ["production", "Production"], ["transform", "Transform"],
             ["view", "View"], ["other", "Other"]],
            "Type",
            required=True,
            search=True),
        "account_id":
        fields.Many2One("account.account",
                        "Inventory Account",
                        multi_company=True,
                        search=True),
        "track_id":
        fields.Many2One("account.track.categ",
                        "Tracking Category",
                        search=True),
        "balance":
        fields.Decimal("Inventory Cost", function="get_balance"),
        "active":
        fields.Boolean("Active"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "products":
        fields.One2Many("stock.balance", "location_id", "Product Stock"),
        "stock_moves":
        fields.One2Many(
            "stock.move",
            None,
            "Stock Moves",
            condition=
            '["or",["location_from_id","=",id],["location_to_id","=",id]]'),
        "description":
        fields.Text("Description"),
        "balance_90d":
        fields.Json("Balance 90d", function="get_balance_90d"),
        "parent_id":
        fields.Many2One("stock.location",
                        "Parent Location",
                        condition=[["type", "=", "view"]]),
        "company_id":
        fields.Many2One("company", "Company"),
        "contact_id":
        fields.Many2One("contact", "Contact"),
        "company2_id":
        fields.Many2One("company", "Company #2"),
        "address_id":
        fields.Many2One("address", "Address"),
    }
    _order = "name"
    _defaults = {
        "active": True,
        "company_id": lambda *a: get_active_company(),
    }

    def get_balance(self, ids, context={}):
        db = database.get_connection()
        q = "SELECT location_from_id,location_to_id,SUM(cost_amount) AS amount FROM stock_move WHERE (location_from_id IN %s OR location_to_id IN %s) AND state='done'"
        args = [tuple(ids), tuple(ids)]
        if context.get("date_to"):
            q += " AND date<=%s"
            args.append(context["date_to"] + " 23:59:59")
        q += " GROUP BY location_from_id,location_to_id"
        res = db.query(q, *args)
        vals = {id: 0 for id in ids}
        for r in res:
            if r.location_from_id in vals:
                vals[r.location_from_id] -= r.amount or 0
            if r.location_to_id in vals:
                vals[r.location_to_id] += r.amount or 0
        return vals

    def get_balance_90d(self, ids, context={}, nocache=False):
        if not nocache:
            min_ctime = time.strftime("%Y-%m-%d 00:00:00")
            vals = get_model("field.cache").get_value("stock.location",
                                                      "balance_90d",
                                                      ids,
                                                      min_ctime=min_ctime)
            remain_ids = [id for id in ids if id not in vals]
            if remain_ids:
                res = self.get_balance_90d(remain_ids,
                                           context=context,
                                           nocache=True)
                for id, data in res.items():
                    vals[id] = data
                    get_model("field.cache").set_value("stock.location",
                                                       "balance_90d", id, data)
            return vals
        print(
            "#########################################################################"
        )
        print("location.get_balance_90d CACHE MISS", ids)
        date_from = datetime.date.today() - datetime.timedelta(days=90)
        date_to = datetime.date.today()
        db = database.get_connection()
        vals = {}
        for id in ids:
            balance = self.get_balance(
                [id], context={"date_to": date_from.strftime("%Y-%m-%d")})[id]
            q = "SELECT date,location_from_id,location_to_id,cost_amount FROM stock_move WHERE (location_from_id=%s OR location_to_id=%s) AND date>%s AND date<=%s AND state='done' ORDER BY date"
            res = db.query(q, id, id, date_from.strftime("%Y-%m-%d 23:59:59"),
                           date_to.strftime("%Y-%m-%d 23:59:59"))
            d = date_from
            data = []
            for r in res:
                while d.strftime("%Y-%m-%d 23:59:59") < r.date:
                    data.append([time.mktime(d.timetuple()) * 1000, balance])
                    d += datetime.timedelta(days=1)
                if r.location_to_id == id and r.location_from_id != id:
                    balance += r.cost_amount or 0
                elif r.location_from_id == id and r.location_to_id != id:
                    balance -= r.cost_amount or 0
            while d <= date_to:
                data.append([time.mktime(d.timetuple()) * 1000, balance])
                d += datetime.timedelta(days=1)
            vals[id] = data
        return vals

    def compute_balance(self,
                        ids,
                        product_id,
                        date=None,
                        lot_id=None,
                        container_id=None,
                        uom_id=None):
        print("compute_balance", ids, product_id, date)
        prod = get_model("product").browse(product_id)
        if not uom_id:
            uom_id = prod.uom_id.id
        db = database.get_connection()
        q = "SELECT uom_id,SUM(qty) AS total_qty,SUM(cost_amount) AS total_amount,SUM(qty2) AS total_qty2 FROM stock_move WHERE location_to_id IN %s AND product_id=%s AND state='done'"
        args = [tuple(ids), product_id]
        if date:
            q += " AND date<=%s"
            args.append(date)
        if lot_id:
            q += " AND lot_id=%s"
            args.append(lot_id)
        if container_id:
            q += " AND container_to_id=%s"
            args.append(container_id)
        q += " GROUP BY uom_id"
        res = db.query(q, *args)
        in_qty = 0
        in_amount = 0
        in_qty2 = 0
        for r in res:
            qty = get_model("uom").convert(r.total_qty, r.uom_id, uom_id)
            in_qty += qty
            in_amount += r.total_amount or 0
            in_qty2 += r.total_qty2 or 0
        q = "SELECT uom_id,SUM(qty) AS total_qty,SUM(cost_amount) AS total_amount,SUM(qty2) AS total_qty2 FROM stock_move WHERE location_from_id IN %s AND product_id=%s AND state='done'"
        args = [tuple(ids), product_id]
        if date:
            q += " AND date<=%s"
            args.append(date)
        if lot_id:
            q += " AND lot_id=%s"
            args.append(lot_id)
        if container_id:
            q += " AND container_from_id=%s"
            args.append(container_id)
        q += " GROUP BY uom_id"
        res = db.query(q, *args)
        out_qty = 0
        out_amount = 0
        out_qty2 = 0
        for r in res:
            qty = get_model("uom").convert(r.total_qty, r.uom_id, uom_id)
            out_qty += qty
            out_amount += r.total_amount or 0
            out_qty2 += r.total_qty2 or 0
        return {
            "bal_qty": in_qty - out_qty,
            "bal_amount": in_amount - out_amount,
            "bal_qty2": in_qty2 - out_qty2,
        }

    def get_contents(self, ids, context={}):
        print("location.get_contents", ids)
        t0 = time.time()
        obj = self.browse(ids)[0]  # XXX
        date_to = context.get("date")
        product_categ_id = context.get("product_categ_id")
        if product_categ_id:
            # Categs deprecated
            product_ids = get_model("product").search(
                [["categs.id", "=", product_categ_id]])
            prod_ids = get_model("product").search(
                [["categ_id", "=", product_categ_id]])
            product_ids = list(set(product_ids + prod_ids))
        else:
            product_ids = None
        tots = get_model("stock.balance").get_totals(product_ids=product_ids,
                                                     location_ids=[obj.id],
                                                     date_to=date_to)
        prod_tots = {}

        def get_sum(prod,
                    lot=None,
                    loc_from=None,
                    loc_to=None,
                    cont_from=None,
                    cont_to=None):
            tot_qty = 0
            tot_amt = 0
            tot_qty2 = 0
            for (lot_id, loc_from_id, cont_from_id, loc_to_id,
                 cont_to_id), (qty, amt, qty2) in prod_tots[prod].items():
                if (lot and lot_id != lot) or (not lot and lot_id):
                    continue
                if loc_from and loc_from_id != loc_from:
                    continue
                if loc_to and loc_to_id != loc_to:
                    continue
                if cont_from and cont_from_id != cont_from:
                    continue
                if cont_to and cont_to_id != cont_to:
                    continue
                tot_qty += qty
                tot_amt += amt
                tot_qty2 += qty2 or 0
            return tot_qty, tot_amt, tot_qty2

        prod_locs = set()
        for (prod_id, lot_id, loc_from_id, cont_from_id, loc_to_id,
             cont_to_id), (qty, amt, qty2) in tots.items():
            prod_locs.add((prod_id, lot_id, loc_from_id, cont_from_id))
            prod_locs.add((prod_id, lot_id, loc_to_id, cont_to_id))
            prod_tots.setdefault(prod_id, {})[(lot_id, loc_from_id,
                                               cont_from_id, loc_to_id,
                                               cont_to_id)] = (qty, amt, qty2)
        print("num prod_locs:", len(prod_locs))
        contents = {}
        for prod_id, lot_id, loc_id, cont_id in prod_locs:
            if loc_id != obj.id:
                continue
            qty_in, amt_in, qty2_in = get_sum(prod=prod_id,
                                              lot=lot_id,
                                              loc_to=loc_id,
                                              cont_to=cont_id)
            qty_out, amt_out, qty2_out = get_sum(prod=prod_id,
                                                 lot=lot_id,
                                                 loc_from=loc_id,
                                                 cont_from=cont_id)
            qty = qty_in - qty_out
            amt = amt_in - amt_out
            qty2 = qty2_in - qty2_out
            if qty <= 0:
                continue
            contents[(prod_id, lot_id, cont_id)] = (qty, amt, qty2)
        t1 = time.time()
        print("get_contents finished in %d ms" % ((t1 - t0) * 1000))
        return contents
Ejemplo n.º 5
0
class Product(Model):
    _name = "product"
    _string = "Product"
    _audit_log = True
    _key = ["code", "state", "company_id"]
    _order = "code,name"
    _export_name_field = "code"
    _history = True
    _fields = {
        "name":
        fields.Char("Name",
                    required=True,
                    search=True,
                    translate=True,
                    size=256),
        "code":
        fields.Char("Code", required=True, search=True),
        "type":
        fields.Selection([["stock", "Stockable"], ["consumable", "Consumable"],
                          ["service", "Service"], ["master", "Master"],
                          ["bundle", "Bundle"]],
                         "Product Type",
                         required=True,
                         search=True),
        "uom_id":
        fields.Many2One("uom", "Default UoM", required=True, search=True),
        "parent_id":
        fields.Many2One("product", "Master Product"),
        "categ_id":
        fields.Many2One("product.categ", "Product Category", search=True),
        "description":
        fields.Text("Description", translate=True),
        "purchase_price":
        fields.Decimal("Purchase Price", scale=6),
        "sale_price":
        fields.Decimal("List Price (Sales Invoice UoM)", scale=6),
        "sale_price_order_uom":
        fields.Decimal("List Price (Sales Order UoM)",
                       scale=6,
                       function="get_sale_price_order_uom"),
        "tags":
        fields.Many2Many("tag", "Tags"),
        "image":
        fields.File("Image"),
        "cost_method":
        fields.Selection(
            [["standard", "Standard Cost"], ["average", "Weighted Average"],
             ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"),
        "cost_price":
        fields.Decimal("Cost Price", scale=6),
        "stock_in_account_id":
        fields.Many2One("account.account",
                        "Stock Input Account",
                        multi_company=True),  # XXX: deprecated
        "stock_out_account_id":
        fields.Many2One("account.account",
                        "Stock Output Account",
                        multi_company=True),  # XXX: deprecated
        "cogs_account_id":
        fields.Many2One("account.account",
                        "Cost Of Goods Sold Account",
                        multi_company=True),
        "stock_account_id":
        fields.Many2One("account.account",
                        "Inventory Account",
                        multi_company=True),
        "purchase_account_id":
        fields.Many2One("account.account",
                        "Purchase Account",
                        multi_company=True),
        "purchase_tax_id":
        fields.Many2One("account.tax.rate", "Purchase Tax"),
        "supplier_id":
        fields.Many2One("contact", "Default Supplier"),  # XXX: deprecated
        "sale_account_id":
        fields.Many2One("account.account", "Sales Account",
                        multi_company=True),
        "sale_return_account_id":
        fields.Many2One("account.account",
                        "Sales Returns Account",
                        multi_company=True),
        "sale_promotion_account_id":
        fields.Many2One("account.account",
                        "Sales Promotion Account",
                        multi_company=True),
        "sale_tax_id":
        fields.Many2One("account.tax.rate", "Sales Tax"),
        "sale_promotion_tax_id":
        fields.Many2One("account.tax.rate", "Promotion Tax"),
        "location_id":
        fields.Many2One("stock.location", "Warehouse"),  # XXX: deprecated
        "bin_location":
        fields.Char("Bin Location"),
        "update_balance":
        fields.Boolean("Update Balance"),
        "active":
        fields.Boolean("Active"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "categs":
        fields.Many2Many("product.categ",
                         "Other Categories"),  # XXX: deprecated
        "attributes":
        fields.One2Many("product.attribute.value", "product_id", "Attributes"),
        "variants":
        fields.One2Many("product", "parent_id", "Variants"),
        #"variant_values": fields.One2Many("product.custom.option.variant.value","product_id","Variant Values"),
        "custom_options":
        fields.Many2Many("product.custom.option", "Custom Options"),
        "images":
        fields.One2Many("product.image", "product_id", "Images"),
        "store_type_id":
        fields.Many2One("store.type", "Storage Type"),
        "brand_id":
        fields.Many2One("product.brand", "Brand", search=True),
        "related_products":
        fields.Many2Many("product",
                         "Related Products",
                         relfield="product1_id",
                         relfield_other="product2_id"),
        "min_sale_qty":
        fields.Decimal("Min Sale Qty"),
        "sale_unit_qty":
        fields.Decimal("Sale Unit Qty"),
        "shelf_life":
        fields.Decimal("Shelf Life (Days)"),
        "weight":
        fields.Decimal("Weight (Kg)"),
        "volume":
        fields.Decimal("Volume (M^3)"),
        "width":
        fields.Decimal("Width"),
        "height":
        fields.Decimal("Height"),
        "length":
        fields.Decimal("Length"),
        "packing_size":
        fields.Char("Packing Size"),
        "details":
        fields.Text("Product Details", translate=True),
        "details2":
        fields.Text("Product Details (2)", translate=True),
        "details2_label":
        fields.Char("Product Details Label (2)"),
        "details3":
        fields.Text("Product Details (3)", translate=True),
        "details3_label":
        fields.Char("Product Details Label (3)"),
        "other_url":
        fields.Char("Product URL", size=256),
        "purchase_currency_id":
        fields.Many2One("currency", "Purchase Currency"),
        "purchase_currency_rate":
        fields.Decimal("Purchase Currency Rate", scale=6),
        "purchase_duty_percent":
        fields.Decimal("Import Duty (%)"),
        "purchase_ship_percent":
        fields.Decimal("Shipping Charge (%)"),
        "landed_cost":
        fields.Decimal("Landed Cost",
                       function="get_landed_cost",
                       function_multi=True),
        "landed_cost_conv":
        fields.Decimal("Landed Cost (Conv)",
                       function="get_landed_cost",
                       function_multi=True),
        "gross_profit":
        fields.Decimal("Gross Profit (%)"),
        "auto_list_price":
        fields.Decimal("Auto List Price", function="get_auto_list_price"),
        "max_discount":
        fields.Decimal("Max Discount (%)", function="get_max_discount"),
        "price_index":
        fields.Decimal("Price Index", function="get_price_index"),
        "price_notes":
        fields.Text("Notes"),
        "price_date":
        fields.Date("Price Date"),
        "pricelist_items":
        fields.One2Many("price.list.item", "product_id", "Pricelist Items"),
        "procure_method":
        fields.Selection([["mto", "Make To Order"], ["mts", "Make To Stock"]],
                         "Procurement Method"),
        "supply_method":
        fields.Selection(
            [["purchase", "Purchase"], ["production", "Production"]],
            "Supply Method"),
        "can_sell":
        fields.Boolean("Can Sell"),
        "can_purchase":
        fields.Boolean("Can Purchase"),
        "id":
        fields.Integer("Database ID", readonly=True),  # MTS
        "create_time":
        fields.DateTime("Create Time", readonly=True),  # MTS
        "supplier_product_code":
        fields.Char("Supplier Product Code"),  # XXX: deprecated
        "require_qty2":
        fields.Boolean("Require Secondary Qty"),
        "qty2_factor":
        fields.Decimal("UoM -> Secondary Qty Factor", scale=6),
        "replacements":
        fields.Many2Many("product",
                         "Replacement Products",
                         reltable="m2m_product_replacement",
                         relfield="product1_id",
                         relfield_other="product2_id"),
        "suppliers":
        fields.One2Many("product.supplier", "product_id", "Suppliers"),
        "max_qty_loss":
        fields.Decimal("Max Qty Loss"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "ship_methods":
        fields.Many2Many("ship.method", "Shipping Methods"),
        "ecom_discount_percent":
        fields.Decimal("Ecom. Discount Percent"),  # XXX: deprecated
        "ecom_special_price":
        fields.Decimal("Ecom. Special Price"),  # XXX: deprecated
        #"ecom_sale_price": fields.Decimal("Ecom. Sale Price", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated
        #"ecom_has_discount": fields.Decimal("Ecom. Has Discount", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated
        "variant_attributes":
        fields.Json("Variant Attributes", function="get_variant_attributes"),
        "company_id":
        fields.Many2One("company", "Company"),
        "sale_channels":
        fields.Many2Many("sale.channel", "Sales Channels"),
        "customer_price":
        fields.Decimal("Customer Price",
                       function="get_customer_price",
                       function_multi=True),
        "customer_has_discount":
        fields.Decimal("Customer Has Discount",
                       function="get_customer_price",
                       function_multi=True),
        "customer_discount_text":
        fields.Decimal("Customer Discount Text",
                       function="get_customer_price",
                       function_multi=True),
        "customer_discount_percent":
        fields.Decimal("Customer Discount Percent",
                       function="get_customer_price",
                       function_multi=True),
        "sale_company_id":
        fields.Many2One("company", "Sold By"),
        "groups":
        fields.Many2Many("product.group", "Groups"),
        "payment_methods":
        fields.Many2Many("payment.method", "Payment Methods"),
        "locations":
        fields.One2Many("product.location", "product_id", "Warehouses"),
        "stock_qty":
        fields.Decimal("Total Stock Qty", function="get_stock_qty"),
        "stock_lots":
        fields.Many2Many("stock.lot",
                         "Lots In Stock",
                         function="get_stock_lots"),
        "state":
        fields.Selection([["draft", "Draft"], ["approved", "Approved"]],
                         "Status"),
        "sale_uom_id":
        fields.Many2One("uom", "Sales Order UoM"),
        "sale_invoice_uom_id":
        fields.Many2One("uom", "Sales Invoice UoM"),
        "sale_to_stock_uom_factor":
        fields.Decimal("Sales Order -> Stock Uom Conversion Factor", scale=6),
        "sale_to_invoice_uom_factor":
        fields.Decimal("Sales Order -> Sales Invoice Uom Conversion Factor",
                       scale=6),
        "purchase_uom_id":
        fields.Many2One("uom", "Purchase Order UoM"),
        "purchase_invoice_uom_id":
        fields.Many2One("uom", "Purchase Invoice UoM"),
        "purchase_to_stock_uom_factor":
        fields.Decimal("Purchase Order -> Stock Uom Conversion Factor",
                       scale=6),
        "purchase_to_invoice_uom_factor":
        fields.Decimal(
            "Purchase Order -> Purchase Invoice Uom Conversion Factor",
            scale=6),
        "purchase_lead_time":
        fields.Integer("Purchasing Lead Time (Days)"),
        "purchase_min_qty":
        fields.Decimal("Purchase Minimum Qty"),
        "purchase_qty_multiple":
        fields.Decimal("Purchase Qty Multiple"),
        "mfg_lead_time":
        fields.Integer("Manufacturing Lead Time (Days)"),
        "mfg_min_qty":
        fields.Decimal("Manufacturing Minimum Qty"),
        "mfg_qty_multiple":
        fields.Decimal("Manufacturing Qty Multiple"),
        #"purchase_price_uom_id": fields.Many2One("uom", "Purchase Price UoM"), # not needed?
        #"sale_price_uom_id": fields.Many2One("uom", "List Price UoM"), # not needed?
        "events":
        fields.Many2Many("sale.event", "Events"),
        "is_published":
        fields.Boolean("Publish Product"),
        "require_lot":
        fields.Boolean("Require Lot"),
        "lot_select":
        fields.Selection([["fifo", "FIFO"], ["fefo", "FEFO"]],
                         "Lot Selection"),
        "components":
        fields.One2Many("product.component", "product_id",
                        "Bundle Components"),
        "approve_date":
        fields.DateTime("Approve Date"),
        "service_items":
        fields.One2Many("service.item", "product_id", "Service Items"),
        "lots":
        fields.One2Many("stock.lot", "product_id", "Lots"),
        "stock_plan_horizon":
        fields.Integer("Inventory Planning Horizon (days)"),  # XXX: deprecated
        "ecom_hide_qty":
        fields.Boolean("Hide Stock Qty From Website"),
        "ecom_hide_unavail":
        fields.Boolean("Hide From Website When Out Of Stock"),
        "ecom_no_order_unavail":
        fields.Boolean("Prevent Orders When Out Of Stock"),
        "ecom_select_lot":
        fields.Boolean("Customers Can Select Lot When Ordering"),
        "product_origin":
        fields.Char("Product Origin"),
        "stock_balances":
        fields.One2Many("stock.balance", "product_id", "Stock Balances"),
        "check_lot_neg_stock":
        fields.Boolean("Check Lot Negative Stock"),
        "sale_lead_time_nostock":
        fields.Integer("Sale Lead Time When Out Of Stock (Days)"),
        "ship_methods":
        fields.Many2Many("ship.method", "Shipping Methods"),
        "delivery_weekdays":
        fields.Char("Delivery Weekday Constraints"),
    }

    _defaults = {
        "update_balance": True,
        "active": True,
        "can_sell": False,
        "can_purchase": False,
        "company_id": lambda *a: access.get_active_company(),
        "state": "draft",
    }

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.code:
                name = "[%s] %s" % (obj.code, obj.name)
            else:
                name = obj.name
            vals.append((obj.id, name, obj.image))
        return vals

    def name_search(self, name, condition=None, context={}, limit=None, **kw):
        print("condition", condition)
        search_mode = context.get("search_mode")
        print("##############################")
        print("search_mode", search_mode)
        if search_mode == "suffix":
            cond = [["code", "=ilike", "%" + name]]
        elif search_mode == "prefix":
            cond = [["code", "=ilike", name + "%"]]
        else:
            cond = [["code", "ilike", name]]
        if condition:
            cond = [cond, condition]
        ids1 = self.search(cond, limit=limit)
        if search_mode == "suffix":
            cond = [["name", "=ilike", "%" + name]]
        elif search_mode == "prefix":
            cond = [["name", "=ilike", name + "%"]]
        else:
            cond = [["name", "ilike", name]]
        if condition:
            cond = [cond, condition]
        ids2 = self.search(cond, limit=limit)
        ids = list(set(ids1 + ids2))
        return self.name_get(ids, context=context)

    def copy(self, ids, context={}):
        replace_id = context.get("replace_id")
        obj = self.browse(ids)[0]
        code = obj.code
        if not replace_id:
            for i in range(1, 10):
                code = obj.code + " (%s)" % i
                res = self.search([["code", "=", code]])
                if not res:
                    break
        vals = {
            "name": obj.name,
            "code": code,
            "type": obj.type,
            "uom_id": obj.uom_id.id,
            #"parent_id": obj.parent_id.id, XXX
            "description": obj.description,
            "image": obj.image,
            "categ_id": obj.categ_id.id,
            "categs": [("set", [c.id for c in obj.categs])],
            "supply_method": obj.supply_method,
            "procure_method": obj.procure_method,
            "can_sell": obj.can_sell,
            "can_purchase": obj.can_purchase,
            "sale_uom_id": obj.sale_uom_id.id,
            "sale_invoice_uom_id": obj.sale_invoice_uom_id.id,
            "sale_to_stock_uom_factor": obj.sale_to_stock_uom_factor,
            "sale_to_invoice_uom_factor": obj.sale_to_invoice_uom_factor,
            "purchase_uom_id": obj.purchase_uom_id.id,
            "purchase_invoice_uom_id": obj.purchase_invoice_uom_id.id,
            "purchase_to_stock_uom_factor": obj.purchase_to_stock_uom_factor,
            "purchase_to_invoice_uom_factor":
            obj.purchase_to_invoice_uom_factor,
            "purchase_price": obj.purchase_price,
            "purchase_account_id": obj.purchase_account_id.id,
            "purchase_tax_id": obj.purchase_tax_id.id,
            "supplier_id": obj.supplier_id.id,
            "sale_price": obj.sale_price,
            "sale_account_id": obj.sale_account_id.id,
            "sale_tax_id": obj.sale_tax_id.id,
            "sale_return_account_id": obj.sale_return_account_id.id,
            "sale_promotion_account_id": obj.sale_promotion_account_id.id,
            "sale_promotion_tax_id": obj.sale_promotion_tax_id.id,
            "cost_method": obj.cost_method,
            "stock_in_account_id": obj.stock_in_account_id.id,
            "stock_out_account_id": obj.stock_out_account_id.id,
            "bin_location": obj.bin_location,
            "sale_company_id": obj.sale_company_id.id,
            "attributes": [],
        }
        vals["attributes"] = [("delete_all", )]
        for attr in obj.attributes:
            vals["attributes"].append(("create", {
                "attribute_id": attr.attribute_id.id,
                "option_id": attr.option_id.id
            }))
        vals["pricelist_items"] = [("delete_all", )]
        for item in obj.pricelist_items:
            vals["pricelist_items"].append(("create", {
                "list_id": item.list_id.id,
                "price": item.price
            }))
        vals["images"] = [("delete_all", )]
        for image in obj.images:
            vals["images"].append(("create", {
                "image": image.image,
                "title": image.title
            }))
        print("vals", vals)
        if replace_id:
            self.write([replace_id], vals)
            new_id = replace_id
        else:
            new_id = self.create(vals)
        return {
            "next": {
                "name": "product",
                "mode": "form",
                "active_id": new_id,
            },
            "flash": "New product copied from %s" % obj.name,
        }

    def get_landed_cost(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = Decimal(obj.purchase_price or 0) * Decimal(
                1 + (obj.purchase_duty_percent or 0) /
                100) * Decimal(1 + (obj.purchase_ship_percent or 0) / 100)
            amt_cur = amt * Decimal(obj.purchase_currency_rate or 1)
            vals[obj.id] = {
                "landed_cost": amt,
                "landed_cost_conv": amt_cur,
            }
        return vals

    def get_auto_list_price(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.landed_cost_conv and obj.gross_profit and obj.gross_profit != 100:
                vals[obj.id] = obj.landed_cost_conv / (1 -
                                                       obj.gross_profit / 100)
            else:
                vals[obj.id] = None
        return vals

    def get_max_discount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.sale_price and obj.landed_cost_conv:
                vals[obj.id] = max(0, (obj.sale_price - obj.landed_cost_conv) /
                                   obj.sale_price * 100)
            else:
                vals[obj.id] = None
        return vals

    def get_price_index(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.sale_price and obj.landed_cost_conv:
                vals[obj.id] = obj.sale_price / obj.landed_cost_conv
            else:
                vals[obj.id] = None
        return vals

    def update_prices(self, context={}):
        print("update_prices")
        data = context["data"]
        purchase_price = data.get("purchase_price")
        purchase_currency_rate = data.get("purchase_currency_rate")
        purchase_duty_percent = data.get("purchase_duty_percent")
        purchase_ship_percent = data.get("purchase_ship_percent")
        if purchase_price:
            landed_cost = purchase_price * \
                (1 + (purchase_duty_percent or 0) / 100) * (1 + (purchase_ship_percent or 0) / 100)
            landed_cost_conv = landed_cost * (purchase_currency_rate or 1)
        else:
            landed_cost = None
            landed_cost_conv = None
        gross_profit = data.get("gross_profit")
        if landed_cost_conv and gross_profit and gross_profit != 100:
            auto_list_price = landed_cost_conv / (1 - gross_profit / 100)
        else:
            auto_list_price = None
        sale_price = data.get("sale_price")
        if sale_price and landed_cost_conv:
            max_discount = max(0, (sale_price - landed_cost_conv) /
                               sale_price * 100)
            price_index = sale_price / landed_cost
        else:
            max_discount = None
            price_index = None
        data.update({
            "landed_cost": landed_cost,
            "landed_cost_conv": landed_cost_conv,
            "auto_list_price": auto_list_price,
            "max_discount": max_discount,
            "price_index": price_index,
        })
        return data

    def get_ecom_sale_price(self, ids, context={}):
        raise Exception("Deprecated!")
        vals = {}
        for obj in self.browse(ids):
            if obj.ecom_discount_percent:
                p = (obj.sale_price
                     or 0) * (1 - obj.ecom_discount_percent / 100)
                has_disc = True
            elif obj.ecom_special_price:
                p = obj.ecom_special_price
                has_disc = True
            else:
                p = obj.sale_price or 0
                has_disc = False
            vals[obj.id] = {
                "ecom_sale_price": p,
                "ecom_has_discount": has_disc,
            }
        return vals

    def create_variants(self, ids, context={}):  # deprecated
        print("##################################")
        print("product.create_variants", ids)
        obj = self.browse(ids[0])
        if obj.type != "master":
            raise Exception("Not a master product")
        if not obj.custom_options:
            raise Exception("No custom options for this product")
        variants = [{}]
        for opt in obj.custom_options:
            new_variants = []
            for variant in variants:
                for opt_val in opt.values:
                    new_variant = variant.copy()
                    new_variant[opt.code] = opt_val.code
                    new_variants.append(new_variant)
            variants = new_variants
        print("variants", len(variants), variants)
        count = 1
        for variant in variants:
            vals = {
                "code": "%s_VARIANT_%.2d" % (obj.code, count),
                "name": obj.name,
                "type": "stock",
                "uom_id": obj.uom_id.id,
                "parent_id": obj.id,
                "location_id": obj.location_id.id,
                "attributes": [],
            }
            for k, v in variant.items():
                name = "_CUST_OPT_" + k
                res = get_model("product.attribute").search(
                    [["name", "=", name]])
                if res:
                    attr_id = res[0]
                else:
                    attr_id = get_model("product.attribute").create(
                        {"name": name})
                vals["attributes"].append(("create", {
                    "attribute_id": attr_id,
                    "value": v,
                }))
            get_model("product").create(vals)
            count += 1
        return {
            "flash": "%d variants created" % len(variants),
        }

    def get_variant_attributes(self, ids, context={}):
        print("get_variant_attributes", ids)
        vals = {}
        for obj in self.browse(ids):
            attrs = []
            attr_options = {}
            for variant in obj.variants:
                for attr in variant.attributes:
                    if not attr.attribute_id or not attr.option_id:
                        continue
                    attr_code = attr.attribute_id.code
                    attr_name = attr.attribute_id.name
                    attrs.append((attr_name, attr_code))
                    opt_code = attr.option_id.code
                    opt_name = attr.option_id.name
                    opt_sequence = attr.option_id.sequence
                    if attr_code == "color":
                        opt_image = variant.image or attr.option_id.image
                    else:
                        opt_image = attr.option_id.image
                    #attr_options.setdefault(attr_code,[]).append((opt_sequence,opt_name,opt_code,opt_image))
                    attr_options.setdefault(attr_code, []).append(
                        (opt_sequence, opt_name, opt_code))
            attrs = list(set(attrs))
            res = []
            for attr_name, attr_code in sorted(attrs):
                attr_vals = {
                    "code": attr_code,
                    "name": attr_name,
                    "values": [],
                }
                attr_options[attr_code] = list(set(attr_options[attr_code]))
                #for opt_sequence,opt_name,opt_code,opt_image in sorted(attr_options[attr_code]):
                for opt_sequence, opt_name, opt_code in sorted(
                        attr_options[attr_code]):
                    attr_vals["values"].append({
                        "sequence": opt_sequence,
                        "code": opt_code,
                        "name": opt_name,
                        #"image": opt_image,
                    })
                res.append(attr_vals)
            vals[obj.id] = res
        print("vals", vals)
        return vals

    def get_customer_price(self, ids, context={}):  # XXX: make it faster
        pricelist_id = context.get("pricelist_id")
        pricelist_ids = context.get("pricelist_ids")
        if pricelist_ids is None and pricelist_id:
            pricelist_ids = [pricelist_id]
        vals = {}
        for obj in self.browse(ids):
            sale_price = None
            discount_text = None
            discount_percent = None
            if pricelist_ids:
                min_sale_price = None
                for item in obj.pricelist_items:
                    if item.list_id.id in pricelist_ids:
                        sale_price = (item.price or 0)
                        if min_sale_price is None or sale_price < min_sale_price:
                            min_sale_price = sale_price
                            discount_text = item.discount_text
                            discount_percent = item.discount_percent
                sale_price = min_sale_price
            if sale_price is None:
                sale_price = (obj.sale_price or 0)
            has_discount = sale_price < (obj.sale_price or 0)
            vals[obj.id] = {
                "customer_price": sale_price,
                "customer_has_discount": has_discount,
                "customer_discount_text": discount_text,
                "customer_discount_percent": discount_percent,
            }
        return vals

    def get_stock_qty(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            qty = 0
            for loc in obj.locations:
                qty += loc.stock_qty
            vals[obj.id] = qty
        return vals

    def get_stock_lots(self, ids, context={}):
        db = database.get_connection()
        res = db.query(
            "SELECT b.product_id,b.location_id,p.sale_price,l.id AS lot_id,l.number,l.weight,SUM(b.qty_virt) AS qty FROM stock_balance b JOIN stock_lot l ON l.id=b.lot_id JOIN product p ON p.id=b.product_id WHERE b.product_id IN %s GROUP BY b.product_id,b.location_id,p.sale_price,l.id,l.number,l.weight ORDER BY l.expiry_date,l.received_date",
            tuple(ids))
        lots = {}
        prod_lots = {}
        for r in res:
            k = (r.product_id, r.lot_id)
            if k not in lots:
                lot_vals = {
                    "id": r.lot_id,
                    "qty": 0,
                }
                lots[k] = lot_vals
                prod_lots.setdefault(r.product_id, []).append(lot_vals)
            lots[k]["qty"] += r.qty
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = [
                l["id"] for l in prod_lots.get(obj.id, []) if l["qty"] > 0
            ]
        return vals

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft", "is_published": False})  #XXX

    def approve(self, ids, context={}):
        for obj in self.browse(ids):
            res = self.search([["code", "=", obj.code],
                               ["state", "=", "approved"],
                               ["company_id", "=", obj.company_id.id]])
            if res:
                repl_id = res[0]
                obj.copy(context={"replace_id": repl_id})
                obj.write({"active": False})
            else:
                vals = {
                    "state": "approved",
                }
                if not obj.parent_id:
                    group_ids = []  #XXX
                    for group in obj.groups:
                        group_ids.append(group.id)
                    res = get_model("product.group").search(
                        [["code", "=", "new"]])
                    if res and res[0] not in group_ids:
                        group_ids.append(res[0])
                    vals.update({
                        "is_published": True,
                        "groups": [("set", group_ids)]
                    })
                if not obj.approve_date:
                    t = time.strftime("%Y-%m-%d %H:%M:%S")
                    vals.update({"approve_date": t})
                obj.write(vals)
        return {
            "flash": "Products approved",
        }

    def ecom_preview(self, ids, context={}):
        prod_id = ids[0]
        return {
            "next": {
                "type": "url",
                "url": "/ecom_product?product_id=%s" % prod_id,
            }
        }

    def get_sale_price_order_uom(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            factor = obj.sale_to_invoice_uom_factor or 1
            vals[obj.id] = math.ceil((obj.sale_price or 0) * factor)
        return vals

    def create_thumbnails(self, ids, context={}):
        print("Product.create_thumbnails", ids)
        for obj in self.browse(ids):
            if not obj.image:
                continue
            dbname = database.get_active_db()
            if not dbname:
                return None
            fdir = os.path.join(os.getcwd(), "static", "db", dbname, "files")
            path = os.path.join(fdir, obj.image)
            basename, ext = os.path.splitext(obj.image)
            res = "," in basename
            if not res:
                rand = base64.urlsafe_b64encode(os.urandom(8)).decode()
                res = os.path.splitext(obj.image)
                basename, ext = res
                fname2 = basename + "," + rand + ext
                #rename image
                dest_path = fdir + "/" + fname2
                print("destination path and file name ", dest_path)
                cmd = "cp %s %s" % (path, dest_path)
                os.system(cmd)
                obj.write({
                    'image': fname2,
                })
                utils.create_thumbnails(fname2)
            else:
                print("called", obj.image)
                utils.create_thumbnails(obj.image)
Ejemplo n.º 6
0
class PettyCash(Model):
    _name = "petty.cash"
    _multi_company = True
    _key = ["number", "company_id"]
    _name_field = "number"
    _string = "Petty Cash"
    _fields = {
        "company_id":
        fields.Many2One("company", "Company", readonly=True, required=True),
        "number":
        fields.Char("Number", search=True),
        "date":
        fields.Date("Date", search=True),
        "state":
        fields.Selection(
            [["draft", "Draft"], ["posted", "Posted"], ["voided", "Voided"]],
            "Status",
            search=True),
        "fund_id":
        fields.Many2One("petty.cash.fund",
                        "Petty Cash Fund",
                        condition=[["state", "=", "approved"]],
                        search=True),
        "type":
        fields.Selection([["in", "Receive"], ["out", "Payment"]], "Type"),
        "receive_type":
        fields.Selection([["in", "IN"], ["out", "OUT"]], "Type"),
        "amount":
        fields.Decimal("Amount"),
        "amount_bal":
        fields.Decimal(
            "Current Balance",
            readonly=True),  ##pull Current Balance from petty cash fund
        "note":
        fields.Text("Memo", search=True),
        "analytic_account_id":
        fields.Many2One("account.account", "Analytic Account"),
        "account_id":
        fields.Many2One("account.account",
                        "Petty Cash Account",
                        search=True,
                        readonly=True),
        "cash_account_id":
        fields.Many2One("account.account", "From Account"),
        "journal_id":
        fields.Many2One("account.journal", "Journal", readonly=True),
        "move_id":
        fields.Many2One("account.move", "Journal Entry"),
        "sequence_id":
        fields.Many2One("sequence", "Number Sequence"),
        "track_id":
        fields.Many2One("account.track.categ",
                        "Track-1",
                        condition=[["type", "=", "1"]],
                        readonly=True),
        "track2_id":
        fields.Many2One("account.track.categ",
                        "Track-2",
                        condition=[["type", "=", "2"]],
                        readonly=True),
        "number_receive":
        fields.Many2One("petty.cash",
                        "Petty Cash Receive",
                        readonly=True,
                        search=True),  ##check petty cash receive remain
        "number_payment":
        fields.Many2One("petty.cash",
                        "Petty Cash peyment waiting",
                        readonly=True),  ##check petty cash payment waiting
        "sequence_id":
        fields.Many2One("sequence", "Number Sequence"),
        "employee_id":
        fields.Many2One("hr.employee", "Employee"),
        #"tax_type": fields.Selection([["tax_ex", "Tax Exclusive"], ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]],"Tax Type"),
        "lines":
        fields.One2Many("petty.cash.line", "petty_cash_id", "Payment"),
        "taxes":
        fields.One2Many("petty.cash.tax",
                        "petty_cash_id",
                        "Vat Line",
                        condition=[["tax_type", "=", "vat"]]),
        "wht_taxes":
        fields.One2Many("petty.cash.tax",
                        "petty_cash_id",
                        "WHT Line",
                        condition=[["tax_type", "=", "wht"]]),
        "amount_subtotal":
        fields.Decimal("Subtotal", function="get_amounts",
                       function_multi=True),
        "amount_tax":
        fields.Decimal("Tax Amount",
                       function="get_amounts",
                       function_multi=True),
        "amount_wht":
        fields.Decimal("Withholding Tax Amount",
                       function="get_amounts",
                       function_multi=True),
        "amount_total":
        fields.Decimal("Total Amount",
                       function="get_amounts",
                       function_multi=True),
        "product_id":
        fields.Many2One("product", "Product", search=True),
        "currency_id":
        fields.Many2One("currency", "Currency"),
        "cash_account_currency_id":
        fields.Many2One("currency", "Currency"),
        "number_receive_total":
        fields.Json("Number of Payment Receive",
                    function="get_number_receive"),
    }

    def _get_number(self, context={}):
        type = context.get("type")
        seq_id = context.get("sequence_id")
        if not seq_id:
            if type == "in":
                seq_type = "petty_cash_receive"
            elif type == "out":
                seq_type = "petty_cash_payment"
            else:
                return
            #seq_type = "petty_cash_receive"
            seq_id = get_model("sequence").find_sequence(name=seq_type,
                                                         context=context)
            if not seq_id:
                return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    def _get_type(self, context={}):
        return context.get("type")

    def _get_journal_id(self, context={}):
        settings = get_model("settings").browse(1)
        return settings.petty_cash_journal_id.id

    def _get_num_receive(self, context={}):
        receive_ptc = get_model("petty.cash.fund").search(
            [['number_receive', '!=', None]])
        return receive_ptc

    _defaults = {
        "number": _get_number,
        "company_id": lambda *a: get_active_company(),
        "date": lambda *a: datetime.strftime(datetime.today(), "%Y-%m-%d"),
        "state": "draft",
        "type": _get_type,
        "tax_type": "tax_in",
        "journal_id": _get_journal_id,
        "note": "",
        "number_receive_total": _get_num_receive,
    }

    def get_number_receive(self, ids, context={}):
        print("Hello ===========================>")
        res = {}
        for obj in self.browse(ids):
            receive_ptc = get_model("petty.cash.fund").search(
                [['number_receive', '!=', None]])
            res[obj.id] = receive_ptc
        return res

    def write(self, ids, vals, **kw):
        super().write(ids, vals, **kw)
        self.function_store(ids)
        return ids

    def create(self, vals, **kw):
        context = kw.get('context')
        print("create function")
        new_id = super().create(vals, **kw)
        return new_id

    def onchange_fund(self, context={}):
        data = context["data"]
        fund_id = get_model("petty.cash.fund").browse(data["fund_id"])
        if fund_id.number_receive.number_payment:
            if fund_id.number_receive.number_payment.state == "draft":
                amount = fund_id.number_receive.number_payment.amount_total
                amount_bal = fund_id.acc_bal - fund_id.number_receive.number_payment.amount_total
            else:
                amount = fund_id.max_amt - fund_id.acc_bal
                amount_bal = fund_id.acc_bal
        else:
            amount = fund_id.max_amt - fund_id.acc_bal
            amount_bal = fund_id.acc_bal
        data["account_id"] = fund_id.account_id.id
        data["track_id"] = fund_id.track_id.id
        data["track2_id"] = fund_id.track2_id.id
        data["currency_id"] = fund_id.currency_id.id
        data["amount"] = amount
        data["amount_bal"] = amount_bal
        return data

    def onchange_fund_pay(self, context={}):
        data = context["data"]
        fund_id = get_model("petty.cash.fund").browse(int(data.get("fund_id")))
        data["number_receive"] = fund_id.number_receive.id
        data["amount_bal"] = fund_id.acc_bal
        if data.get("type") == "out":
            for line in data["lines"]:
                line[
                    'track_id'] = fund_id.track_id.id if fund_id.track_id else None
                line[
                    'track2_id'] = fund_id.track2_id.id if fund_id.track2_id else None
        print("data from onchange product")
        print(data)
        return data

    def update_line(self, context={}):
        data = context["data"]
        path = context['path']
        line = get_data_path(data, path, parent=True)
        try:
            fund_id = get_model("petty.cash.fund").browse(data.get("fund_id"))
        except:
            raise Exception("Not found Petty Cash Fund")
        amount_bal = fund_id.acc_bal or 0
        if not line.get("qty", None):
            line["qty"] = 1
            data["amount_bal"] = amount_bal
        line["amount"] = (line.get("unit_price") or 0) * (line.get("qty") or 0)
        data = self.update_amounts(data)
        data["amount_bal"] = amount_bal - (data["amount_total"]
                                           or 0) if amount_bal else 0
        return data

    def post(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.calc_taxes()
        fund = get_model("petty.cash.fund")
        fund_id = obj.fund_id
        if fund_id:
            if obj.type == "out" and fund_id.acc_bal < fund_id.min_amt:
                raise Exception(
                    "Information Current Balance less than Minimum Amount in Petty Cash Fund your select!! Please make Petty Cash Receive"
                )
        if obj.type == "in":
            if obj.amount > (obj.fund_id.max_amt -
                             obj.fund_id.acc_bal) or obj.amount <= 0:
                raise Exception("Invalid amount")
            cond = [
                ["id", "=", obj.fund_id.id],
            ]
            for s in fund.search_browse(cond):
                s.write({
                    "number_receive": obj.id,
                })
            move_vals = {
                "journal_id":
                obj.journal_id.id,
                "number":
                obj.number,
                "date":
                obj.date,
                "narration":
                "{} ; {}".format(obj.note, obj.fund_id.name)
                if obj.note or obj.note != "" else
                "Petty Cash received ; {}".format(obj.fund_id.name),
                "related_id":
                "petty.cash,%s" % obj.id,
                "company_id":
                obj.company_id.id,
                'lines': [],
            }

            ## petty cash account
            line_vals = {
                'description':
                "{} ; {}".format(obj.note, obj.fund_id.name)
                if obj.note or obj.note != "" else
                "Petty Cash received ; {}".format(obj.fund_id.name),
                'account_id':
                obj.account_id.id,
                'track_id':
                obj.track_id.id,
                'track2_id':
                obj.track2_id.id,
                'debit':
                obj.amount,
                'credit':
                0,
                'due_date':
                obj.date,
            }
            move_vals['lines'].append(('create', line_vals))

            ## cash account
            line_vals = {
                'description':
                "{} ; {}".format(obj.note, obj.fund_id.name)
                if obj.note or obj.note != "" else
                "Petty Cash received ; {}".format(obj.fund_id.name),
                # 'description': "{} ; {}".format(obj.note, obj.fund_id.name),
                'account_id':
                obj.cash_account_id.id,
                'debit':
                0,
                'credit':
                obj.amount,
                'due_date':
                obj.date,
            }
            move_vals['lines'].append(('create', line_vals))

        else:
            if obj.amount_total > obj.fund_id.max_amt:
                raise Exception("Total amount is over limit.")
            if obj.amount_total > obj.fund_id.acc_bal:
                raise Exception("Account Balance is not enough.")
            if obj.amount_bal < 0:
                raise Exception("Petty Cash Payment Over Petty Cash Receive")
            if fund_id.number_receive:
                fund_id.number_receive.write({
                    "amount_bal": obj.amount_bal,
                })
            if obj.number_receive:
                obj.number_receive.write({"number_payment": obj.id})
            move_vals = {
                "journal_id":
                obj.journal_id.id,
                "number":
                obj.number,
                "date":
                obj.date,
                "narration":
                "{} ; {}".format(obj.note, obj.fund_id.name)
                if obj.note or obj.note != "" else
                "Petty Cash payment ; {}".format(obj.fund_id.name),
                "related_id":
                "petty.cash,%s" % obj.id,
                "company_id":
                obj.company_id.id,
                'lines': [],
            }

            # lines
            for line in obj.lines:
                line_vals = {
                    'description':
                    "{} ; {}".format(obj.note, obj.fund_id.name)
                    if obj.note or obj.note != "" else
                    "Petty Cash payment ; {}".format(obj.fund_id.name),
                    'account_id':
                    line.account_id.id,
                    'debit':
                    line.base_amt,
                    'track_id':
                    line.track_id.id,
                    'track2_id':
                    line.track2_id.id,
                    'credit':
                    0,
                    'due_date':
                    obj.date,
                }
                move_vals['lines'].append(('create', line_vals))

            # vat
            for tax in obj.taxes:
                line_vals = {
                    'description':
                    "{} ; {} ; {}".format(obj.note, obj.fund_id.name,
                                          tax.tax_comp_id.name) if obj.note or
                    obj.note != "" else "Petty Cash payment ; {} ; {}".format(
                        obj.fund_id.name, tax.tax_comp_id.name),
                    'account_id':
                    tax.tax_comp_id.account_id.id,
                    'debit':
                    tax.tax_amount,
                    'contact_id':
                    tax.contact_id.id,
                    'tax_comp_id':
                    tax.tax_comp_id.id,
                    'tax_base':
                    tax.base_amount,
                    'tax_no':
                    tax.tax_no,
                    'track_id':
                    line.track_id.id,
                    'track2_id':
                    line.track2_id.id,
                    'credit':
                    0,
                    'due_date':
                    obj.date,
                }
                move_vals['lines'].append(('create', line_vals))

            # wht
            for wht in obj.wht_taxes:
                line_vals = {
                    'description':
                    "{} ; {} ; {}".format(obj.note, obj.fund_id.name,
                                          wht.tax_comp_id.name) if obj.note or
                    obj.note != "" else "Petty Cash payment ; {} ; {}".format(
                        obj.fund_id.name, wht.tax_comp_id.name),
                    #'description': "{} ; {} ; {} ".format(obj.note, obj.fund_id.name, wht.tax_comp_id.name),
                    'account_id':
                    wht.tax_comp_id.account_id.id,
                    'debit':
                    0,
                    'contact_id':
                    wht.contact_id.id,
                    'tax_comp_id':
                    wht.tax_comp_id.id,
                    'tax_base':
                    wht.base_amount,
                    'tax_no':
                    wht.tax_no,
                    'track_id':
                    line.track_id.id,
                    'track2_id':
                    line.track2_id.id,
                    'credit':
                    wht.tax_amount,
                    'due_date':
                    obj.date,
                }
                move_vals['lines'].append(('create', line_vals))

            # fund
            line_vals = {
                'description':
                "{} ; {}".format(obj.note, obj.fund_id.name)
                if obj.note or obj.note != "" else
                "Petty Cash payment ; {}".format(obj.fund_id.name),
                'account_id':
                obj.fund_id.account_id.id,
                'debit':
                0,
                'track_id':
                obj.fund_id.track_id.id,
                'track2_id':
                obj.fund_id.track2_id.id,
                'credit':
                obj.amount_total,
                'due_date':
                obj.date,
            }
            move_vals['lines'].append(('create', line_vals))

        move_id = get_model('account.move').create(move_vals)
        move = get_model('account.move').browse(move_id)
        move.post()
        obj.write({
            'move_id': move_id,
            'state': 'posted',
        })

    def view_journal_entry(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            'next': {
                'name': 'journal_entry',
                'active_id': obj.move_id.id,
                'mode': 'form',
            },
        }

    def onchange_sequence(self, context={}):
        data = context["data"]
        num = self._get_number(
            context={
                "type": data["type"],
                "date": data["date"],
                "sequence_id": data["sequence_id"]
            })
        data["number"] = num
        return data

    def get_amounts(self, ids, context={}):
        res = {}
        obj = self.browse(ids)[0]
        settings = get_model("settings").browse(1)
        subtotal = tax_amount = wht_amount = total = 0
        for line in obj.lines:
            tax_id = line.tax_id
            tax = 0
            wht = 0
            if tax_id and line.tax_type != "no_tax":
                base_amts = get_model("account.tax.rate").compute_base_wht(
                    tax_id, line.amount, tax_type=line.tax_type)
                for amt, comp_type in base_amts:
                    if comp_type == "vat":
                        base_amt = amt
                        tax_comps = get_model(
                            "account.tax.rate").compute_taxes_wht(
                                tax_id, amt, when="petty_cash")
                        for comp_id, tax_amt in tax_comps.items():
                            tax += tax_amt
                    elif comp_type == "wht":
                        tax_comps = get_model(
                            "account.tax.rate").compute_taxes_wht(
                                tax_id, amt, when="petty_cash", wht=True)
                        for comp_id, tax_amt in tax_comps.items():
                            wht += tax_amt
            else:
                base_amt = line.amount
            subtotal += base_amt
            tax_amount += tax
            wht_amount += wht
            total += base_amt + tax
        vals = {
            "amount_subtotal":
            get_model("currency").round(settings.currency_id.id, subtotal),
            "amount_tax":
            get_model("currency").round(settings.currency_id.id, tax_amount),
            "amount_wht":
            abs(wht_amount),
            "amount_total":
            total + wht_amount,
        }
        res[obj.id] = vals
        return res

    def update_amounts(self, data, context={}):
        res = {}
        settings = get_model("settings").browse(1)
        subtotal = tax_amount = wht_amount = total = 0
        for line in data["lines"]:
            if line.get("tax_id"):
                tax_id = get_model("account.tax.rate").browse(
                    line.get("tax_id"))
                tax = 0
                wht = 0
                if tax_id and line.get("tax_type") not in ["no_tax", None]:
                    base_amts = get_model("account.tax.rate").compute_base_wht(
                        tax_id,
                        line.get("amount"),
                        tax_type=line.get("tax_type"))
                    for amt, comp_type in base_amts:
                        if comp_type == "vat":
                            base_amt = amt
                            tax_comps = get_model(
                                "account.tax.rate").compute_taxes_wht(
                                    tax_id, amt, when="petty_cash")
                            for comp_id, tax_amt in tax_comps.items():
                                tax += tax_amt
                        elif comp_type == "wht":
                            tax_comps = get_model(
                                "account.tax.rate").compute_taxes_wht(
                                    tax_id, amt, when="petty_cash", wht=True)
                            for comp_id, tax_amt in tax_comps.items():
                                wht += tax_amt
                            base_amt = line[
                                "amount"] if base_amt == 0 else base_amt
                else:
                    base_amt = line["amount"]

                line["base_amt"] = base_amt
                subtotal += base_amt
                tax_amount += tax
                wht_amount += wht
                total += base_amt + tax
            else:
                line["base_amt"] = line.get("amount")
                subtotal += line.get("amount") or 0
                total += line.get("amount") or 0
        data["amount_subtotal"] = get_model("currency").round(
            settings.currency_id.id, subtotal),
        data["amount_tax"] = get_model("currency").round(
            settings.currency_id.id, tax_amount),
        data["amount_wht"] = abs(wht_amount),
        data["amount_total"] = total + wht_amount
        return data

    def to_draft(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.move_id:
            obj.move_id.to_draft()
            obj.move_id.delete()
        obj.write({
            'state': 'draft',
        }, context=context)
        obj.taxes.delete()
        obj.wht_taxes.delete()

        if obj.type == "in":
            obj.fund_id.write({"number_receive": None})

        return

    def void(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.move_id.void()
        obj.write({
            'state': 'voided',
        }, context=context)
        for tax in obj.taxes:
            tax.write({
                'base_amount': 0,
                'tax_amount': 0,
            })
        for tax in obj.wht_taxes:
            tax.write({
                'base_amount': 0,
                'tax_amount': 0,
            })
        if obj.type == "in":
            obj.fund_id.write({"number_receive": None})

    def copy(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "date": obj.date,
            "type": obj.type,
            "fund_id": obj.fund_id.id,
            "number_receive": obj.number_receive.id,
            "amount_bal": obj.amount_bal,
            "company_id": obj.company_id.id,
            "journal_id": obj.journal_id.id,
            "amount_subtotal": obj.amount_subtotal,
            "amount_tax": obj.amount_tax,
            "amount_wht": obj.amount_wht,
            "amount_total": obj.amount_total,
            "track_id": obj.track_id.id,
            "track2_id": obj.track2_id.id,
            "amount": obj.amount,
            "cash_account_id": obj.cash_account_id.id,
            "account_id": obj.account_id.id,
        }
        lines = []
        for line in obj.lines:
            line_vals = {
                "product_id": line.product_id.id,
                "description": line.description,
                "account_id": line.account_id.id,
                "unit_price": line.unit_price,
                "qty": line.qty,
                "amount": line.amount,
                "tax_id": line.tax_id.id,
                "tax_type": line.tax_type,
                "tax_date": line.tax_date,
                "tax_invoice": line.tax_invoice,
                "contact_id": line.contact_id.id,
                "track_id": line.track_id.id,
                "track2_id": line.track2_id.id,
            }
            lines.append(line_vals)
        vals["lines"] = [("create", v) for v in lines]
        pmt_id = self.create(vals, context={"type": vals["type"]})
        return {
            "next": {
                "name": "view_petty_cash",
                "active_id": pmt_id,
            },
            "flash": "New payment %d copied from %d" % (pmt_id, obj.id),
        }

    def calc_taxes(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.taxes.delete()
        obj.wht_taxes.delete()
        settings = get_model("settings").browse(1)
        taxes = {}
        tax_nos = []
        total_amt = total_base = total_tax = 0
        for line in obj.lines:
            tax_id = line.tax_id
            if tax_id and line.tax_type not in ["no_tax", None]:
                base_amts = get_model("account.tax.rate").compute_base_wht(
                    tax_id, line.amount, tax_type=line.tax_type)
                for amt, comp_type in base_amts:
                    if settings.rounding_account_id:
                        base_amt = get_model("currency").round(
                            settings.currency_id.id, amt)
                    if comp_type == "vat":
                        base_amt = amt
                        tax_comps = get_model(
                            "account.tax.rate").compute_taxes_wht(
                                tax_id, base_amt, when="petty_cash")
                        for comp_id, tax_amt in tax_comps.items():
                            key = (comp_id, line.contact_id.id,
                                   line.tax_invoice)
                            tax_vals = taxes.setdefault(
                                key, {
                                    "tax_amt": 0,
                                    "base_amt": 0
                                })
                            tax_vals["tax_amt"] += tax_amt
                            tax_vals["base_amt"] += base_amt
                    elif comp_type == "wht":
                        tax_comps = get_model(
                            "account.tax.rate").compute_taxes_wht(
                                tax_id, base_amt, when="petty_cash", wht=True)
                        for comp_id, tax_amt in tax_comps.items():
                            key = (comp_id, line.contact_id.id,
                                   line.tax_invoice)
                            tax_vals = taxes.setdefault(
                                key, {
                                    "tax_amt": 0,
                                    "base_amt": 0
                                })
                            tax_vals["tax_amt"] += tax_amt
                            tax_vals["base_amt"] += base_amt
            else:
                base_amt = line.amount
        for (comp_id, contact_id, tax_invoice), tax_vals in taxes.items():
            comp = get_model("account.tax.component").browse(comp_id)
            acc_id = comp.account_id.id
            if not acc_id:
                raise Exception("Missing account for tax component %s" %
                                comp.name)
            ctx = {"date": obj.date}
            vals = {
                "petty_cash_id":
                obj.id,
                "tax_comp_id":
                comp_id,
                "base_amount":
                get_model("currency").round(settings.currency_id.id,
                                            tax_vals["base_amt"]),
                "tax_amount":
                get_model("currency").round(settings.currency_id.id,
                                            abs(tax_vals["tax_amt"])),
                'tax_date':
                obj.date,
                'tax_type':
                comp.type,
                'tax_no':
                get_model("petty.cash.line").gen_tax_no(
                    context=ctx) if comp.type == "wht" else tax_invoice,
                'contact_id':
                contact_id,
            }
            get_model("petty.cash.tax").create(vals)

    def view_petty_cash(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.type == "out":
            action = "petty_cash_payment"
            layout = "petty_cash_payment_form"
        elif obj.type == "in":
            action = "petty_cash_receive"
            layout = "petty_cash_receive_form"
        return {
            "next": {
                "name": action,
                "mode": "form",
                "form_view_xml": layout,
                "active_id": obj.id,
            }
        }

    def onchange_date(self, context={}):
        data = context["data"]
        ctx = {
            "type": data["type"],
            "date": data["date"],
        }
        number = self._get_number(context=ctx)
        data["number"] = number
        return data

    def delete(self, ids, **kw):
        for obj in self.browse(ids):
            if obj.state == "voided" or obj.state == "posted":
                raise Exception("These items can not be Deleted")
        super().delete(ids, **kw)

    def onchange_product(self, context={}):
        data = context['data']
        path = context['path']
        line = get_data_path(data, path, parent=True)
        product_model = get_model("product").browse(line.get("product_id"))
        line["description"] = product_model.description
        if product_model.get("purchase_account_id"):
            line["account_id"] = product_model.purchase_account_id.id
        else:
            line[
                "account_id"] = product_model.categ_id.purchase_account_id.id if product_model.categ_id.purchase_account_id else None

        if product_model.purchase_tax_id is not None and product_model.purchase_tax_id.id:
            line["tax_id"] = product_model.purchase_tax_id.id
        else:
            if product_model.categ_id.purchase_tax_id is not None and product_model.categ_id.purchase_tax_id.id:
                line["tax_id"] = product_model.categ_id.purchase_tax_id.id
            else:
                line["tax_id"] = None
        if line['tax_id']:
            line['tax_type'] = "tax_ex"
        else:
            line['tax_type'] = None
        if data.get("fund_id"):
            fund_id = get_model("petty.cash.fund").browse(
                int(data.get("fund_id")))
            if fund_id:
                data[
                    "number_receive"] = fund_id.number_receive.id if fund_id.number_receive else None
                data["amount_bal"] = fund_id.acc_bal
                if data.get("type") == "out":
                    line[
                        'track_id'] = fund_id.track_id.id if fund_id.track_id else None
                    line[
                        'track2_id'] = fund_id.track2_id.id if fund_id.track2_id else None
        return data

    def onchange_cash_account_id(self, context={}):
        obj = context['data']
        cash_account = get_model("account.account").browse(
            obj['cash_account_id'])
        if not cash_account:
            return
        else:
            obj['cash_account_currency_id'] = cash_account.currency_id.id
        return obj
Ejemplo n.º 7
0
class Account(Model):
    _name = "account.account"
    _string = "Account"
    _audit_log = True
    _key = ["code", "company_id"]
    _export_field = "code"
    _multi_company = True

    def get_code_number(self, ids, context={}):
        res={}
        for obj in self.browse(ids):
            code_number=''.join([s for s in obj.code if s.isdigit()]) or '0'
            res[obj.id]=code_number
        return res

    _fields = {
        "code": fields.Char("Account Code", required=True, search=True, index=True),
        "code_number": fields.Char("Code Number", function="get_code_number"),
        "name": fields.Char("Account Name", size=256, required=True, search=True, translate=True),
        "type": fields.Selection([
            ["_group", "Assets"],
            ["cash", "Cash"],
            ["cheque", "Cheque"],
            ["bank", "Bank Account"],
            ["receivable", "Receivable"],
            ["cur_asset", "Current Asset"],
            ["fixed_asset", "Fixed Asset"],
            ["noncur_asset", "Non-current Asset"],
            ["_group", "Liabilities"],
            ["payable", "Payable"],
            ["cust_deposit", "Customer Deposit"],
            ["cur_liability", "Current Liability"],
            ["noncur_liability", "Non-current Liability"],
            ["_group", "Equity "],#add some space for prevent import wrong type
            ["equity", "Equity"],
            ["_group", "Expenses"],
            ["cost_sales", "Cost of Sales"],
            ["expense", "Expense"],
            ["other_expense", "Other Expense"],
            ["_group", "Income"],
            ["revenue", "Revenue"],
            ["other_income", "Other Income"],
            ["_group", "Other"],
            ["view", "View"],
            ["other", "Other"]], "Type", required=True, search=True, index=True),

        "parent_id": fields.Many2One("account.account", "Parent", condition=[["type", "=", "view"]], search=True),
        "bank_type": fields.Selection([["bank", "Bank Account"], ["credit_card", "Credit Card"], ["paypal", "Paypal"]], "Bank Type"),
        "description": fields.Text("Description"),
        "tax_id": fields.Many2One("account.tax.rate", "Tax"),
        "balance": fields.Decimal("Accounting Balance", function="get_balance", function_multi=True),
        "debit": fields.Decimal("Debit", function="get_balance", function_multi=True),
        "credit": fields.Decimal("Credit", function="get_balance", function_multi=True),
        "balance_statement": fields.Decimal("Statement Balance", function="get_balance_statement"),
        "bank_name": fields.Char("Bank Name"),
        "currency_id": fields.Many2One("currency", "Account Currency", required=True, search=True),
        "bank_no": fields.Char("Bank Account Number"),
        "creditcard_no": fields.Char("Credit Card Number"),
        "balance_90d": fields.Json("Balance 90d", function="get_balance_90d"),
        "active": fields.Boolean("Active"),
        "unrec_num": fields.Integer("Unreconciled Items", function="get_unrec_num"),
        "enable_payment": fields.Boolean("Enable payments to this account"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "balance_cur": fields.Decimal("Accounting Balance (Cur)", function="get_balance", function_multi=True),
        "company_id": fields.Many2One("company", "Company"),
        "fixed_asset_type_id": fields.Many2One("account.fixed.asset.type", "Fixed Asset Type"),
        "statements": fields.One2Many("account.statement", "account_id", "Bank Statements"),
        "move_lines": fields.One2Many("account.move.line", "account_id", "Account Transactions", order="move_date desc"),
        "require_contact": fields.Boolean("Require Contact"),
        "require_tax_no": fields.Boolean("Require Tax No"),
        "require_track": fields.Boolean("Require Tracking Category"),
        "require_track2": fields.Boolean("Require Secondary Tracking Category"),
        "company_currency_id": fields.Many2One("currency", "Company Currency", function="get_company_currency"),
        "is_cheque": fields.Boolean("Cheque"),
    }
    _order = "code"

    def _get_currency(self, context={}):
        settings = get_model("settings").browse(1)
        return settings.currency_id.id

    _defaults = {
        "active": True,
        "company_id": lambda *a: get_active_company(),
        "currency_id": _get_currency,
    }

    def name_search(self, name, condition=[], limit=None, context={}):
        if name.isdigit():
            cond = [["code", "=ilike", name + "%"], condition]
        else:
            cond = [["or", ["name", "ilike", name], ["code", "=ilike", name + "%"]], condition]
        ids = self.search(cond, limit=limit, context=context)
        return self.name_get(ids, context)

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            name = "[%s] %s" % (obj.code, obj.name)
            vals.append((obj.id, name))
        return vals

    def delete(self, ids, **kw):
        move_ids = []
        move_ids = get_model("account.move").search([["lines.account_id", "in", ids], ["state", "=", "posted"]])
        if move_ids:
            raise Exception("This account can't be deleted because it is used in some posted accounting transactions")
        move_ids = get_model("account.move").search([["lines.account_id", "in", ids]])
        get_model("account.move").delete(move_ids)
        super().delete(ids, **kw)

    def get_balance_statement(self, ids, context={}):
        db = database.get_connection()
        vals = {}
        for id in ids:
            res = db.get(
                "SELECT l.balance FROM account_statement_line l,account_statement s WHERE s.id=l.statement_id AND s.account_id=%s ORDER BY l.date DESC,l.id DESC LIMIT 1", id)
            bal = res.balance if res else 0
            vals[id] = bal
        return vals

    def get_balance(self, ids, context={}, nocache=False):
        print("Account.get_balance", ids, context)
        date_from = context.get("date_from")
        date_to = context.get("date_to")
        track_id = context.get("track_id")
        track2_id = context.get("track2_id")
        currency_id = context.get("currency_id")
        contact_id = context.get("contact_id")
        bank_rec_state = context.get("bank_rec_state")
        excl_date_to = context.get("excl_date_to")
        journal_id = context.get("journal_id")
        print("#########################################################################")
        print("get_balance CACHE MISS", ids)
        db = database.get_connection()

        def _get_balance(ids, date_from=None, date_to=None, track_id=None, track2_id=None, journal_id=None, contact_id=None, bank_rec_state=None, excl_date_to=None, nocache=False):
            if not ids:
                return {}
            if not nocache and not date_from and not date_to and not track_id and not track2_id and not journal_id and not contact_id and not bank_rec_state and not excl_date_to:
                acc_bals = get_model("field.cache").get_value("account.account", "balance", ids)
                remain_ids = [id for id in ids if id not in acc_bals]
                if remain_ids:
                    res = _get_balance(remain_ids, nocache=True)
                    for id, vals in res.items():
                        acc_bals[id] = vals
                        get_model("field.cache").set_value("account.account", "balance", id, vals)
                return acc_bals
            q = "SELECT l.account_id,SUM(l.debit) AS debit,SUM(l.credit) AS credit,SUM(COALESCE(l.amount_cur,l.debit-l.credit)) AS amount_cur FROM account_move_line l JOIN account_move m ON m.id=l.move_id WHERE l.account_id IN %s AND m.state='posted'"
            q_args = [tuple(ids)]
            if date_from:
                q += " AND m.date>=%s"
                q_args.append(date_from)
            if date_to:
                if excl_date_to:
                    q += " AND m.date<%s"
                else:
                    q += " AND m.date<=%s"
                q_args.append(date_to)
            if track_id:
                track_ids = get_model("account.track.categ").search([["id", "child_of", track_id]])
                q += " AND l.track_id IN %s"
                q_args.append(tuple(track_ids))
            if track2_id:
                track2_ids = get_model("account.track.categ").search([["id", "child_of", track2_id]])
                q += " AND l.track2_id IN %s"
                q_args.append(tuple(track2_ids))
            if contact_id:
                q += " AND l.contact_id=%s"
                q_args.append(contact_id)
            if bank_rec_state:
                q += " AND m.state=%s"
                q_args.append(bank_rec_state)
            if journal_id:
                q += " AND m.journal_id=%s"
                q_args.append(journal_id)
            q += " GROUP BY l.account_id"
            res = db.query(q, *q_args)
            id_res = {}
            for r in res:
                id_res[r.account_id] = r
            vals = {}
            for id in ids:
                r = id_res.get(id)
                vals[id] = {
                    "debit": r["debit"] if r else 0,
                    "credit": r["credit"] if r else 0,
                    "balance": r["debit"] - r["credit"] if r else 0,
                    "balance_cur": r["amount_cur"] if r else 0,
                }
            return vals

        def _conv_currency(bals):
            if not bals:
                return {}
            comp_cur = {}
            comp_id = get_active_company()
            for comp in get_model("company").search_browse([]):
                set_active_company(comp.id)
                comp_settings = get_model("settings").browse(1)
                comp_cur[comp.id] = comp_settings.currency_id.id
            set_active_company(comp_id)
            acc_ids = bals.keys()
            res = db.query("SELECT id,code,currency_id,company_id,type FROM account_account WHERE id IN %s", tuple(acc_ids))
            acc_cur = {}
            acc_comp = {}
            acc_rate_type = {}
            rate_sell = ["cash","cheque","bank","receivable","cur_asset","fixed_asset","noncur_asset","revenue","other_income"]
            for r in res:
                if not r["currency_id"]:
                    raise Exception("Missing currency for account %s" % r["code"])
                acc_cur[r["id"]] = r["currency_id"]
                acc_comp[r["id"]] = r["company_id"]
                if r["type"]in rate_sell:
                    acc_rate_type[r["id"]] = "sell"
                else:
                    acc_rate_type[r["id"]] = "buy"
            bals2 = {}
            settings = get_model('settings').browse(1)
            for acc_id, vals in bals.items():
                comp_id = acc_comp.get(acc_id)
                comp_currency_id = comp_cur.get(comp_id) or settings.currency_id.id
                acc_currency_id = acc_cur[acc_id]
                rate_type = acc_rate_type[acc_id]
                bals2[acc_id] = {
                    "debit": get_model("currency").convert(vals["debit"], comp_currency_id, currency_id, date=date_to, rate_type=rate_type),
                    "credit": get_model("currency").convert(vals["credit"], comp_currency_id, currency_id, date=date_to, rate_type=rate_type),
                    "balance": get_model("currency").convert(vals["balance"], comp_currency_id, currency_id, date=date_to, rate_type=rate_type),
                    "balance_cur": get_model("currency").convert(vals["balance_cur"], acc_currency_id, currency_id, date=date_to, rate_type=rate_type),
                }
            return bals2
        acc_bals = _get_balance(ids, date_from=date_from, date_to=date_to, track_id=track_id,
                                track2_id=track2_id, journal_id=journal_id, contact_id=contact_id, bank_rec_state=bank_rec_state, excl_date_to=excl_date_to)
        pl_types = ("revenue", "other_income", "cost_sales", "expense", "other_expense")
        pl_acc_ids = self.search([["type", "in", pl_types]])
        ret_acc_ids = {}
        comp_id = get_active_company()
        for comp in get_model("company").search_browse([]):
            set_active_company(comp.id)
            comp_settings = get_model("settings").browse(1)
            ret_acc_ids[comp.id] = comp_settings.retained_earnings_account_id.id
        set_active_company(comp_id)
        if (set(ids) & set(pl_acc_ids) or set(ids) & set(ret_acc_ids.values())) and not context.get("no_close"):
            pl_acc_comps = {}
            for acc in self.browse(pl_acc_ids):
                pl_acc_comps[acc.id] = acc.company_id.id
            if date_from:
                pl_date_from = get_model("settings").get_fiscal_year_start(date_from)
            else:
                pl_date_from = None
            pl_date_to = get_model("settings").get_fiscal_year_start(date_to)
            pl_date_to = (datetime.datetime.strptime(pl_date_to, "%Y-%m-%d") -
                          datetime.timedelta(days=1)).strftime("%Y-%m-%d")
            if not pl_date_from or pl_date_from <= pl_date_to:
                pl_bals = _get_balance(pl_acc_ids, date_from=pl_date_from, date_to=pl_date_to,
                                       track_id=track_id, track2_id=track2_id, journal_id=journal_id, contact_id=contact_id, bank_rec_state=bank_rec_state)
                ret_amts = {}
                for acc_id, pl_vals in pl_bals.items():
                    comp_id = pl_acc_comps[acc_id]
                    ret_amts.setdefault(comp_id, 0)
                    ret_amts[comp_id] += pl_vals["balance"]
                    if acc_id in acc_bals:
                        acc_vals = acc_bals[acc_id]
                        bal = acc_vals["balance"] - pl_vals["balance"]
                        acc_vals["debit"] = bal > 0 and bal or 0
                        acc_vals["credit"] = bal < 0 and -bal or 0
                        acc_vals["balance"] = bal
                        acc_vals["balance_cur"] = bal
                for comp_id, ret_amt in ret_amts.items():
                    ret_acc_id = ret_acc_ids.get(comp_id)
                    if not ret_acc_id or ret_acc_id not in acc_bals:
                        continue
                    acc_vals = acc_bals[ret_acc_id]
                    bal = acc_vals["balance"] + ret_amt
                    acc_vals["debit"] = bal > 0 and bal or 0
                    acc_vals["credit"] = bal < 0 and -bal or 0
                    acc_vals["balance"] = bal
                    acc_vals["balance_cur"] = bal
        if currency_id:
            acc_bals = _conv_currency(acc_bals)
        return acc_bals

    def get_balance_90d(self, ids, context={}, nocache=False):
        if not nocache:
            min_ctime = time.strftime("%Y-%m-%d 00:00:00")
            vals = get_model("field.cache").get_value("account.account", "balance_90d", ids, min_ctime=min_ctime)
            remain_ids = [id for id in ids if id not in vals]
            if remain_ids:
                res = self.get_balance_90d(remain_ids, context=context, nocache=True)
                for id, data in res.items():
                    vals[id] = data
                    get_model("field.cache").set_value("account.account", "balance_90d", id, data)
            return vals
        print("#########################################################################")
        print("get_balance_90d CACHE MISS", ids)
        date_from = datetime.date.today() - datetime.timedelta(days=90)
        date_to = datetime.date.today()
        db = database.get_connection()
        vals = {}
        for id in ids:
            balance = self.get_balance([id], context={"date_to": date_from.strftime("%Y-%m-%d")})[id]["balance"]
            q = "SELECT move_date,debit,credit FROM account_move_line WHERE account_id=%s AND move_date>%s AND move_date<=%s AND move_state='posted' ORDER BY move_date"
            res = db.query(q, id, date_from, date_to)
            d = date_from
            data = []
            for r in res:
                while d.strftime("%Y-%m-%d") < r["move_date"]:
                    data.append([time.mktime(d.timetuple()) * 1000, balance])
                    d += datetime.timedelta(days=1)
                balance += (r["debit"] or 0) - (r["credit"] or 0)
            while d <= date_to:
                data.append([time.mktime(d.timetuple()) * 1000, balance])
                d += datetime.timedelta(days=1)
            vals[id] = data
        return vals

    def get_template(self, context={}):
        obj = self.browse(int(context["active_id"]))
        if not obj.bank_type:
            return "account_form"
        elif obj.bank_type == "bank":
            return "account_form_bank"
        elif obj.bank_type == "credit_card":
            return "account_form_credit_card"
        elif obj.bank_type == "paypal":
            return "account_form_paypal"

    def get_unrec_num(self, ids, context={}):
        db = database.get_connection()
        res = db.query(
            "SELECT st.account_id,count(*) FROM account_statement_line stl JOIN account_statement st ON st.id=stl.statement_id WHERE stl.state='not_reconciled' AND st.account_id IN %s GROUP BY st.account_id", tuple(ids))
        vals = {}
        for r in res:
            vals[r.account_id] = r.count
        return vals

    def auto_bank_reconcile(self, ids, context={}):
        print("auto_bank_reconcile", ids)
        acc_lines = {}
        for acc_line in get_model("account.move.line").search_browse([["account_id", "in", ids], ["state", "=", "not_reconciled"]], order="move_date"):
            k = "%s %.2f %.2f" % (acc_line.account_id.id, acc_line.debit, acc_line.credit)
            acc_lines.setdefault(k, []).append((acc_line.move_id.date, acc_line.description, acc_line.id))
        recs = {}
        for st_line in get_model("account.statement.line").search_browse([["statement_id.account_id", "in", ids], ["state", "=", "not_reconciled"]], order="date"):
            k = "%s %.2f %.2f" % (st_line.statement_id.account_id.id, st_line.received, st_line.spent)
            st_date = datetime.datetime.strptime(st_line.date, "%Y-%m-%d")
            for acc_date_, acc_desc, acc_line_id in acc_lines.get(k, []):
                acc_date = datetime.datetime.strptime(acc_date_, "%Y-%m-%d")
                date_diff = abs((acc_date - st_date).days)
                desc_match = acc_desc and st_line.description and acc_desc.strip(
                ).lower() == st_line.description.strip().lower()
                prev_rec = recs.get((acc_line_id, st_line.id))
                if not prev_rec or desc_match and not prev_rec["desc_match"] or date_diff < prev_rec["date_diff"]:
                    recs[(acc_line_id, st_line.id)] = {
                        "desc_match": desc_match,
                        "date_diff": date_diff,
                    }
        print("recs", recs)
        for acc_line_id, st_line_id in recs:
            get_model("account.statement.line").write([st_line_id], {"move_lines": [("set", [acc_line_id])]})

    def get_company_currency(self, ids, context={}):
        comp_cur = {}
        comp_id = get_active_company()
        for comp in get_model("company").search_browse([]):
            set_active_company(comp.id)
            comp_settings = get_model("settings").browse(1)
            comp_cur[comp.id] = comp_settings.currency_id.id
        set_active_company(comp_id)
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = comp_cur.get(obj.company_id.id)
        return vals

    def copy(self, ids, context={}):
        obj=self.browse(ids)[0]
        vals={
            'code': '%s (Copy)'%(obj.code),
            'name': '%s (Copy)'%(obj.name),
            'type': obj.type,
            'parent_id': obj.parent_id and obj.parent_id.id,
            'description': obj.description,
            'tax_id': obj.tax_id.id,
            'enable_payment': obj.enable_payment,
            'currency_id': obj.currency_id and obj.currency_id.id,
            'require_contact': obj.require_contact,
            'require_tax_no': obj.require_tax_no,
            'require_track': obj.require_track,
            'require_track2': obj.require_track2,
        }
        new_id=self.create(vals)
        new=self.browse(new_id)
        return {
            'next': {
                'name': 'account',
                'mode': 'form',
                'active_id': new_id,
            },
            'flash': 'Copy account %s to %s successfull'%(obj.name, new.name)
        }
Ejemplo n.º 8
0
class ReportCashSum(Model):
    _name = "report.cash.sum"
    _transient = True
    _fields = {
        "date_from": fields.Date("Date From"),
        "date_to": fields.Date("Date To"),
        "report_action": fields.Json("Report Action", function="get_action"),
    }

    def get_default_action(self, context={}):
        return {
            "name": "report_cash_sum",
        }

    _defaults = {
        "date_from":
        lambda *a: date.today().strftime("%Y-%m-01"),
        "date_to":
        lambda *a: (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d"),
        "report_action":
        get_default_action,
    }

    def get_action(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {}
        vals[obj.id] = {
            "name": "report_cash_sum",
            "context": {
                "date_from": obj.date_from,
                "date_to": obj.date_to,
            }
        }
        return vals

    def run_report(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            "next": {
                "name": "report_cash_sum_page",
                "active_id": obj.id,
            }
        }

    def export_cash_sum(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            "next": {
                "name": "report_cash_sum_xls",
                "date_from": obj.date_from,
                "date_to": obj.date_to,
            }
        }

    def get_data(self, context={}):
        date_from = context.get("date_from")
        if not date_from:
            date_from = date.today().strftime("%Y-%m-01")
        date_from_min1 = (datetime.strptime(date_from, "%Y-%m-%d") -
                          timedelta(days=1)).strftime("%Y-%m-%d")
        date_to = context.get("date_to")
        if not date_to:
            date_to = (date.today() +
                       relativedelta(day=31)).strftime("%Y-%m-%d")
        bank_ids = get_model("account.account").search([["type", "=", "bank"]])
        cash_open = 0
        for obj in get_model("account.account").read(
                bank_ids, ["balance"], context={"date_to": date_from_min1}):
            cash_open += obj["balance"]
        cash_close = 0
        for obj in get_model("account.account").read(
                bank_ids, ["balance"], context={"date_to": date_to}):
            cash_close += obj["balance"]
        db = database.get_connection()
        res = db.query(
            "SELECT a.id,a.name,sum(l.debit-l.credit) AS amount FROM account_move_line l,account_account a WHERE l.move_id IN (SELECT DISTINCT m.id FROM account_move_line l,account_move m,account_account a WHERE m.state='posted' AND m.date>=%s AND m.date<=%s AND m.id=l.move_id AND a.id=l.account_id AND a.type='bank') AND a.id=l.account_id AND a.type!='bank' AND l.reconcile_id IS NULL GROUP BY a.id ORDER BY a.name",
            date_from, date_to)
        accounts = {}
        for r in res:
            accounts[r.id] = {
                "id": r.id,
                "name": r.name,
                "amount": r.amount,
            }
        res = db.query(
            "SELECT a4.id as account_id,a4.name,SUM(l2.debit*(l4.credit-l4.debit)/(l3.debit-l3.credit)) as debit,SUM(l2.credit*(l4.credit-l4.debit)/(l3.debit-l3.credit)) as credit FROM account_move_line l2,account_account a2,account_move_line l3,account_move_line l4,account_account a4 WHERE l2.move_id IN (SELECT DISTINCT m1.id FROM account_move_line l1,account_move m1,account_account a1 WHERE m1.state='posted' AND m1.date>=%s AND m1.date<=%s AND m1.id=l1.move_id AND a1.id=l1.account_id AND a1.type='bank') AND a2.id=l2.account_id AND a2.type!='bank' AND l2.reconcile_id=l3.reconcile_id AND l3.id!=l2.id AND l4.move_id=l3.move_id AND l4.id!=l3.id AND a4.id=l4.account_id AND a4.type!='bank' GROUP BY a4.id",
            date_from, date_to)
        for r in res:
            acc = accounts.setdefault(r.account_id, {
                "id": r.account_id,
                "name": r.name,
                "amount": 0,
            })
            acc["amount"] += r.debit - r.credit
        lines = []
        total = 0
        for acc in sorted(accounts.values(), key=lambda a: a["name"]):
            line = {
                "type": "account",
                "id": acc["id"],
                "string": acc["name"],
                "amount": -acc["amount"],
                "padding": 10,
            }
            lines.append(line)
            total -= acc["amount"]
        line = {
            "type": "group_footer",
            "string": "Net Cash Movement",
            "amount": total,
            "separator": "double",
        }
        lines.append(line)
        data = {
            "date_from": date_from,
            "date_to": date_to,
            "col0": date_to,
            "company_name": context["company_name"],
            "cash_open": cash_open,
            "cash_move": cash_close - cash_open,
            "cash_close": cash_close,
            "lines": lines,
        }
        return data
Ejemplo n.º 9
0
class SaleReturn(Model):
    _name = "sale.return"
    _string = "Sales Return"
    _audit_log = True
    _name_field = "number"
    _multi_company = True
    _key = ["company_id",
            "number"]  # need migration first otherwise can't add constraint...
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "ref":
        fields.Char("Ref", search=True),
        "contact_id":
        fields.Many2One("contact", "Customer", required=True, search=True),
        "date":
        fields.Date("Date", required=True, search=True),
        "state":
        fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"),
                          ("done", "Completed"), ("voided", "Voided")],
                         "Status",
                         required=True),
        "lines":
        fields.One2Many("sale.return.line", "order_id", "Lines"),
        "amount_subtotal":
        fields.Decimal("Subtotal",
                       function="get_amount",
                       function_multi=True,
                       store=True),
        "amount_tax":
        fields.Decimal("Tax Amount",
                       function="get_amount",
                       function_multi=True,
                       store=True),
        "amount_total":
        fields.Decimal("Total",
                       function="get_amount",
                       function_multi=True,
                       store=True),
        "amount_total_discount":
        fields.Decimal("Total Discount",
                       function="get_amount",
                       function_multi=True,
                       store=True),
        "amount_total_words":
        fields.Char("Total Words", function="get_amount_total_words"),
        "amount_total_cur":
        fields.Decimal("Total",
                       function="get_amount",
                       function_multi=True,
                       store=True),
        "qty_total":
        fields.Decimal("Total", function="get_qty_total"),
        "currency_id":
        fields.Many2One("currency", "Currency", required=True),
        "user_id":
        fields.Many2One("base.user", "Owner", search=True),
        "tax_type":
        fields.Selection([["tax_ex", "Tax Exclusive"],
                          ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]],
                         "Tax Type",
                         required=True),
        "invoice_lines":
        fields.One2Many("account.invoice.line", "sale_id", "Invoice Lines"),
        "invoices":
        fields.One2Many("account.invoice", "related_id", "Credit Notes"),
        "pickings":
        fields.Many2Many("stock.picking",
                         "Stock Pickings",
                         function="get_pickings"),
        "is_delivered":
        fields.Boolean("Delivered", function="get_delivered"),
        "is_paid":
        fields.Boolean("Paid", function="get_paid"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "activities":
        fields.One2Many("activity", "related_id", "Activities"),
        "location_id":
        fields.Many2One("stock.location", "Warehouse",
                        search=True),  # XXX: deprecated
        "price_list_id":
        fields.Many2One("price.list",
                        "Price List",
                        condition=[["type", "=", "sale"]]),
        "payment_terms":
        fields.Text("Payment Terms"),
        "delivery_date":
        fields.Date("Due Date"),  # XXX; deprecated
        "due_date":
        fields.Date("Due Date"),
        "team_id":
        fields.Many2One("mfg.team", "Production Team"),
        "ship_method_id":
        fields.Many2One("ship.method", "Shipping Method"),  # XXX: deprecated
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "addresses":
        fields.One2Many("address", "related_id", "Addresses"),
        "bill_address_id":
        fields.Many2One("address", "Billing Address"),
        "ship_address_id":
        fields.Many2One("address", "Shipping Address"),
        "coupon_id":
        fields.Many2One("sale.coupon", "Coupon"),
        "purchase_lines":
        fields.One2Many("purchase.order.line", "sale_id", "Purchase Orders"),
        "production_orders":
        fields.One2Many("production.order", "sale_id", "Production Orders"),
        "other_info":
        fields.Text("Other Information"),
        "company_id":
        fields.Many2One("company", "Company"),
        "production_status":
        fields.Json("Production", function="get_production_status"),
        "ship_term_id":
        fields.Many2One("ship.term", "Shipping Terms"),
        "approved_by_id":
        fields.Many2One("base.user", "Approved By", readonly=True),
        "sequence_id":
        fields.Many2One("sequence", "Number Sequence"),
        "stock_moves":
        fields.One2Many("stock.move", "related_id", "Stock Movements"),
        "agg_amount_total":
        fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "agg_amount_subtotal":
        fields.Decimal("Total Amount w/o Tax",
                       agg_function=["sum", "amount_subtotal"]),
        "agg_est_profit":
        fields.Decimal("Total Estimated Profit",
                       agg_function=["sum", "est_profit"]),
        "agg_act_profit":
        fields.Decimal("Total Actual Profit",
                       agg_function=["sum", "act_profit"]),
        "year":
        fields.Char("Year", sql_function=["year", "date"]),
        "quarter":
        fields.Char("Quarter", sql_function=["quarter", "date"]),
        "month":
        fields.Char("Month", sql_function=["month", "date"]),
        "week":
        fields.Char("Week", sql_function=["week", "date"]),
        "pay_method_id":
        fields.Many2One("payment.method", "Payment Method", search=True),
        "related_id":
        fields.Reference(
            [["sale.quot", "Quotation"], ["ecom.cart", "Ecommerce Cart"],
             ["purchase.order", "Purchase Order"]], "Related To"),
        "orig_sale_id":
        fields.Many2One("sale.order", "Original Sales Order"),
    }

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="sale_return",
                                                     context=context)
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            if not num:
                return None
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    def _get_currency(self, context={}):
        settings = get_model("settings").browse(1)
        return settings.currency_id.id

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "number": _get_number,
        "currency_id": _get_currency,
        "tax_type": "tax_ex",
        "user_id": lambda *a: get_active_user(),
        "company_id": lambda *a: get_active_company(),
    }
    _order = "date desc,number desc"

    def create(self, vals, context={}):
        id = super().create(vals, context)
        self.function_store([id])
        return id

    def write(self, ids, vals, **kw):
        super().write(ids, vals, **kw)
        self.function_store(ids)

    def delete(self, ids, **kw):
        for obj in self.browse(ids):
            if obj.state in ("confirmed", "done"):
                raise Exception("Can not delete sales order in this status")
        super().delete(ids, **kw)

    def get_amount(self, ids, context={}):
        res = {}
        settings = get_model("settings").browse(1)
        for obj in self.browse(ids):
            vals = {}
            subtotal = 0
            tax = 0
            discount = 0
            for line in obj.lines:
                discount += line.amount_discount
                if line.tax_id:
                    line_tax = get_model("account.tax.rate").compute_tax(
                        line.tax_id.id, line.amount, tax_type=obj.tax_type)
                else:
                    line_tax = 0
                tax += line_tax
                if obj.tax_type == "tax_in":
                    subtotal += line.amount - line_tax
                else:
                    subtotal += line.amount
            vals["amount_subtotal"] = subtotal
            vals["amount_tax"] = tax
            vals["amount_total"] = subtotal + tax
            vals["amount_total_cur"] = get_model("currency").convert(
                vals["amount_total"], obj.currency_id.id,
                settings.currency_id.id)
            vals["amount_total_discount"] = discount
            res[obj.id] = vals
        return res

    def get_qty_total(self, ids, context={}):
        res = {}
        for obj in self.browse(ids):
            qty = sum([line.qty for line in obj.lines])
            res[obj.id] = qty or 0
        return res

    def confirm(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.state != "draft":
            raise Exception("Invalid state")
        for line in obj.lines:
            prod = line.product_id
            if prod and prod.type in ("stock", "consumable",
                                      "bundle") and not line.location_id:
                raise Exception("Missing location for product %s" % prod.code)
        obj.write({"state": "confirmed"})
        settings = get_model("settings").browse(1)
        obj.trigger("confirm")
        return {
            "next": {
                "name": "sale_return",
                "mode": "form",
                "active_id": obj.id,
            },
            "flash": "Sales Return %s confirmed" % obj.number,
        }

    def done(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "confirmed":
                raise Exception("Invalid state")
            obj.write({"state": "done"})

    def reopen(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "done":
                raise Exception("Invalid state")
            obj.write({"state": "confirmed"})

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft"})

    def update_amounts(self, context):
        data = context["data"]
        data["amount_subtotal"] = 0
        data["amount_tax"] = 0
        tax_type = data["tax_type"]
        for line in data["lines"]:
            if not line:
                continue
            amt = (line.get("qty") or 0) * (line.get("unit_price") or 0)
            if line.get("discount"):
                disc = amt * line["discount"] / 100
                amt -= disc
            if line.get("discount_amount"):
                amt -= line["discount_amount"]
            line["amount"] = amt
            tax_id = line.get("tax_id")
            if tax_id:
                tax = get_model("account.tax.rate").compute_tax(
                    tax_id, amt, tax_type=tax_type)
                data["amount_tax"] += tax
            else:
                tax = 0
            if tax_type == "tax_in":
                data["amount_subtotal"] += amt - tax
            else:
                data["amount_subtotal"] += amt
        data["amount_total"] = data["amount_subtotal"] + data["amount_tax"]
        return data

    def onchange_product(self, context):
        data = context["data"]
        contact_id = data.get("contact_id")
        if contact_id:
            contact = get_model("contact").browse(contact_id)
        else:
            contact = None
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if not prod_id:
            return {}
        prod = get_model("product").browse(prod_id)
        line["description"] = prod.description or "/"
        line["qty"] = 1
        line["uom_id"] = prod.sale_uom_id.id or prod.uom_id.id
        pricelist_id = data["price_list_id"]
        price = None
        if pricelist_id:
            price = get_model("price.list").get_price(pricelist_id, prod.id, 1)
            price_list = get_model("price.list").browse(pricelist_id)
            price_currency_id = price_list.currency_id.id
        if price is None:
            price = prod.sale_price
            settings = get_model("settings").browse(1)
            price_currency_id = settings.currency_id.id
        if price is not None:
            currency_id = data["currency_id"]
            price_cur = get_model("currency").convert(price, price_currency_id,
                                                      currency_id)
            line["unit_price"] = price_cur
        if prod.sale_tax_id is not None:
            line["tax_id"] = prod.sale_tax_id.id
        if prod.location_id:
            line["location_id"] = prod.location_id.id
        data = self.update_amounts(context)
        return data

    def onchange_qty(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if not prod_id:
            return {}
        prod = get_model("product").browse(prod_id)
        pricelist_id = data["price_list_id"]
        qty = line["qty"]
        price = None
        if pricelist_id:
            price = get_model("price.list").get_price(pricelist_id, prod.id,
                                                      qty)
            price_list = get_model("price.list").browse(pricelist_id)
            price_currency_id = price_list.currency_id.id
        if price is None:
            price = prod.sale_price
            settings = get_model("settings").browse(1)
            price_currency_id = settings.currency_id.id
        if price is not None:
            currency_id = data["currency_id"]
            price_cur = get_model("currency").convert(price, price_currency_id,
                                                      currency_id)
            line["unit_price"] = price_cur
        data = self.update_amounts(context)
        return data

    def onchange_uom(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if not prod_id:
            return {}
        prod = get_model("product").browse(prod_id)
        uom_id = line.get("uom_id")
        if not uom_id:
            return {}
        uom = get_model("uom").browse(uom_id)
        if prod.sale_price is not None:
            line[
                "unit_price"] = prod.sale_price * uom.ratio / prod.uom_id.ratio
        data = self.update_amounts(context)
        return data

    def get_qty_to_deliver(self, ids):
        obj = self.browse(ids)[0]
        sale_quants = {}
        for line in obj.lines:
            prod = line.product_id
            if not prod or prod.type == "service":
                continue
            uom = line.uom_id
            sale_quants.setdefault((prod.id, uom.id), 0)
            sale_quants[(prod.id, uom.id)] += line.qty  # XXX: uom
        done_quants = {}
        for move in obj.stock_moves:
            if move.state == "cancelled":
                continue
            prod = move.product_id
            done_quants.setdefault(prod.id, 0)
            done_quants[prod.id] += move.qty  # XXX: uom
        to_deliver = {}
        for (prod_id, uom_id), qty in sale_quants.items():
            qty_done = done_quants.get(prod_id, 0)
            if qty_done < qty:
                to_deliver[(prod_id, uom_id)] = qty - qty_done
        return to_deliver

    def copy_to_picking(self, ids, context={}):
        id = ids[0]
        obj = self.browse(id)
        pick_vals = {}
        contact = obj.contact_id
        res = get_model("stock.location").search([["type", "=", "customer"]])
        if not res:
            raise Exception("Customer location not found")
        cust_loc_id = res[0]
        res = get_model("stock.location").search([["type", "=", "internal"]])
        if not res:
            raise Exception("Warehouse not found")
        wh_loc_id = res[0]

        for obj_line in obj.lines:
            picking_key = obj_line.ship_method_id and obj_line.ship_method_id.id or 0
            if picking_key in pick_vals: continue
            pick_vals[picking_key] = {
                "type":
                "in",
                "ref":
                obj.number,
                "related_id":
                "sale.return,%s" % obj.id,
                "contact_id":
                contact.id,
                "ship_address_id":
                obj.ship_address_id.id,
                "lines": [],
                "state":
                "draft",
                "ship_method_id":
                obj_line.ship_method_id.id or obj.ship_method_id.id,
            }
            if contact and contact.pick_out_journal_id:
                pick_vals[picking_key][
                    "journal_id"] = contact.pick_out_journal_id.id
        for line in obj.lines:
            picking_key = line.ship_method_id and line.ship_method_id.id or 0
            prod = line.product_id
            if not prod:
                continue
            if prod.type not in ("stock", "consumable"):
                continue
            if line.qty <= 0:
                continue
            qty_remain = (line.qty_stock or line.qty) - line.qty_received
            if qty_remain <= 0:
                continue
            line_vals = {
                "product_id": prod.id,
                "qty": qty_remain,
                "uom_id": prod.uom_id.id if line.qty_stock else line.uom_id.id,
                "location_from_id": cust_loc_id,
                "location_to_id": line.location_id.id or wh_loc_id,
                "related_id": "sale.return,%s" % obj.id,
            }
            pick_vals[picking_key]["lines"].append(("create", line_vals))
        for picking_key, picking_value in pick_vals.items():
            if not picking_value["lines"]: Exception("Nothing left to deliver")
            pick_id = get_model("stock.picking").create(
                picking_value, context={"pick_type": "in"})
            pick = get_model("stock.picking").browse(pick_id)
        return {
            "next": {
                "name": "pick_in",
                "mode": "form",
                "active_id": pick_id,
            },
            "flash":
            "Picking %s created from sales order %s" %
            (pick.number, obj.number),
            "picking_id":
            pick_id,
        }

    def copy_to_credit_note(self, ids, context={}):
        obj = self.browse(ids[0])
        company_id = get_active_company()
        set_active_company(obj.company_id.id)  # XXX
        try:
            ship_method_ids = []
            ship_method_amts = {}
            ship_amt_total = 0
            for line in obj.lines:
                ship_method_ids.append(line.ship_method_id.id)
                ship_method_amts.setdefault(line.ship_method_id.id, 0)
                ship_method_amts[line.ship_method_id.id] += line.amount
                ship_amt_total += line.amount
            ship_method_ids = list(set(ship_method_ids))
            inv_ids = []
            for ship_method_id in ship_method_ids:
                contact = obj.contact_id
                inv_vals = {
                    "type": "out",
                    "inv_type": "credit",
                    "ref": obj.number,
                    "related_id": "sale.return,%s" % obj.id,
                    "contact_id": contact.id,
                    "bill_address_id": obj.bill_address_id.id,
                    "currency_id": obj.currency_id.id,
                    "tax_type": obj.tax_type,
                    "pay_method_id": obj.pay_method_id.id,
                    "lines": [],
                }
                if contact.sale_journal_id:
                    inv_vals["journal_id"] = contact.sale_journal_id.id
                    if contact.sale_journal_id.sequence_id:
                        inv_vals[
                            "sequence_id"] = contact.sale_journal_id.sequence_id.id
                for line in obj.lines:
                    if not line.unit_price:
                        continue
                    if line.ship_method_id.id != ship_method_id:
                        continue
                    prod = line.product_id
                    remain_qty = line.qty - line.qty_invoiced
                    if remain_qty <= 0:
                        continue

                    sale_acc_id = None
                    if prod:
                        #1. get account from product
                        sale_acc_id = prod.sale_return_account_id and prod.sale_return_account_id.id
                        if not sale_acc_id and prod.sale_account_id:
                            sale_acc_id = prod.sale_account_id.id
                        # 2. if not get from master/parent product
                        if not sale_acc_id and prod.parent_id:
                            sale_acc_id = prod.parent_id.sale_account_id.id
                        # 3. if not get from product category
                        categ = prod.categ_id
                        if categ and not sale_acc_id:
                            sale_acc_id = categ.sale_account_id and categ.sale_account_id.id or None

                    #if not sale_acc_id:
                    #raise Exception("Missing sale account for product [%s] " % prod.name )

                    line_vals = {
                        "product_id":
                        prod.id,
                        "description":
                        line.description,
                        "qty":
                        remain_qty,
                        "uom_id":
                        line.uom_id.id,
                        "unit_price":
                        line.unit_price,
                        "discount":
                        line.discount,
                        "discount_amount":
                        line.discount_amount,
                        "account_id":
                        sale_acc_id,
                        "tax_id":
                        line.tax_id.id,
                        "amount":
                        line.qty * line.unit_price *
                        (1 - (line.discount or Decimal(0)) / 100) -
                        (line.discount_amount or Decimal(0)),
                    }
                    inv_vals["lines"].append(("create", line_vals))
                if not inv_vals["lines"]:
                    continue
                inv_id = get_model("account.invoice").create(
                    inv_vals, {
                        "type": "out",
                        "inv_type": "credit"
                    })
                inv_ids.append(inv_id)
            if not inv_ids:
                raise Exception("Nothing to invoice")
            print("inv_ids", inv_ids)
            return {
                "next": {
                    "name": "view_invoice",
                    "active_id": inv_ids[0],
                },
                "flash":
                "Credit note created from sales order %s" % obj.number,
                "invoice_id": inv_ids[0],
            }
        finally:
            set_active_company(company_id)

    def get_delivered(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            is_delivered = True
            for line in obj.lines:
                prod = line.product_id
                if prod.type not in ("stock", "consumable", "bundle"):
                    continue
                remain_qty = (line.qty_stock or line.qty) - line.qty_received
                if remain_qty > 0:
                    is_delivered = False
                    break
            vals[obj.id] = is_delivered
        return vals

    def get_paid(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt_paid = 0
            for inv in obj.invoices:
                if inv.state != "paid":
                    continue
                amt_paid += inv.amount_total
            is_paid = amt_paid >= obj.amount_total
            vals[obj.id] = is_paid
        return vals

    def void(self, ids, context={}):
        for obj in self.browse(ids):
            for pick in obj.pickings:
                if pick.state == "pending":
                    raise Exception(
                        "There are still pending goods issues for this sales order"
                    )
            for inv in obj.invoices:
                if inv.state == "waiting_payment":
                    raise Exception(
                        "There are still invoices waiting payment for this sales order"
                    )
            obj.write({"state": "voided"})

    def copy(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "contact_id": obj.contact_id.id,
            "date": obj.date,
            "ref": obj.ref,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "user_id": obj.user_id.id,
            "lines": [],
        }
        for line in obj.lines:
            line_vals = {
                "product_id": line.product_id.id,
                "description": line.description,
                "qty": line.qty,
                "discount": line.discount,
                "discount_amount": line.discount_amount,
                "uom_id": line.uom_id.id,
                "location_id": line.location_id.id,
                "unit_price": line.unit_price,
                "tax_id": line.tax_id.id,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "sale_return",
                "mode": "form",
                "active_id": new_id,
            },
            "flash":
            "Sales Return %s copied to %s" % (obj.number, new_obj.number),
            "sale_id": new_id,
        }

    def get_invoices(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            inv_ids = []
            for inv_line in obj.invoice_lines:
                inv_id = inv_line.invoice_id.id
                if inv_id not in inv_ids:
                    inv_ids.append(inv_id)
            vals[obj.id] = inv_ids
        return vals

    def get_pickings(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            pick_ids = []
            for move in obj.stock_moves:
                pick_id = move.picking_id.id
                if pick_id not in pick_ids:
                    pick_ids.append(pick_id)
            vals[obj.id] = pick_ids
        return vals

    def onchange_contact(self, context):
        data = context["data"]
        contact_id = data.get("contact_id")
        if not contact_id:
            return {}
        contact = get_model("contact").browse(contact_id)
        data["payment_terms"] = contact.payment_terms
        data["price_list_id"] = contact.sale_price_list_id.id
        data["bill_address_id"] = contact.get_address(pref_type="billing")
        data["ship_address_id"] = contact.get_address(pref_type="shipping")
        return data

    def approve(self, ids, context={}):
        if not check_permission_other("sale_approve_done"):
            raise Exception("Permission denied")
        obj = self.browse(ids)[0]
        user_id = get_active_user()
        obj.write({"approved_by_id": user_id})
        return {
            "next": {
                "name": "sale",
                "mode": "form",
                "active_id": obj.id,
            },
            "flash": "Sales order approved successfully",
        }

    def onchange_sequence(self, context={}):
        data = context["data"]
        context['date'] = data['date']
        seq_id = data["sequence_id"]
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            res = self.search([["number", "=", num]])
            if not res:
                break
            get_model("sequence").increment_number(seq_id, context=context)
        data["number"] = num
        return data

    def get_state_label(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.state == "draft":
                s = "Draft"
            if obj.state == "confirmed":
                s = "Confirmed"
            elif obj.state == "done":
                s = "Completed"
            elif obj.state == "voided":
                s = "Voided"
            else:
                s = "/"
            vals[obj.id] = s
        return vals

    def get_pickings(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            pick_ids = []
            for move in obj.stock_moves:
                pick_ids.append(move.picking_id.id)
            pick_ids = sorted(list(set(pick_ids)))
            vals[obj.id] = pick_ids
        return vals