Pengenaan icon

This commit is contained in:
2026-04-06 19:53:23 +07:00
parent 77efb390e5
commit 1ce4833841
9 changed files with 56 additions and 17 deletions
@@ -10,6 +10,7 @@
'stock',
'account',
'om_account_asset',
'asa_simpin_syariah',
'master_sapi',
'kandang',
],
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -2,6 +2,7 @@
<odoo>
<menuitem id="menu_dairy_management_root"
name="Dairy Asset Management"
web_icon="grt_asset_dairy_management,static/description/icon.png"
sequence="25"/>
<menuitem id="menu_dairy_management_cows"
@@ -538,14 +538,18 @@ Setiap rule minimal berisi:
- `Team Sales`
- `Wilayah` pada level `wilayah.kecamatan`
- `Shipping Product`
- `Tarif per Kg`
Perilaku backend:
- backend hanya menambahkan ongkir otomatis untuk Sales Order yang dibuat dari endpoint frontend
- backend mencari rule berdasarkan kombinasi `team_id + wilayah_id + company`
- jika rule ditemukan, backend menambahkan 1 line produk ongkir otomatis
- harga ongkir mengikuti pricelist Sales Order atau `list_price` produk ongkir
- backend menghitung total berat dari semua line produk: `qty x berat produk`
- nominal ongkir dihitung dengan rumus `total_kg x tarif_per_kg`
- line ongkir otomatis menyimpan ringkasan berat total dan tarif per kg pada deskripsi line
- jika rule tidak ditemukan, pembuatan Sales Order akan ditolak
- jika total berat produk `0`, pembuatan Sales Order akan ditolak
Catatan untuk tim frontend:
@@ -675,6 +679,7 @@ Minimal kirim `partner_id` atau `customer_qr_ref`.
- `order_line` wajib minimal 1 item
- setiap line wajib punya `product_id` dan `product_uom_qty > 0`
- backend otomatis menambahkan line biaya pengiriman untuk order frontend berdasarkan rule `team_id + wilayah_id`
- backend menghitung nominal line ongkir dari `total berat produk x tarif per kg` pada rule
- jika rule ongkir frontend tidak ditemukan, pembuatan order akan ditolak
### Endpoint Draft Order Berdasarkan Jenis Bon
@@ -869,7 +874,9 @@ export async function getJsonSession(url) {
- pilih endpoint draft order sesuai jenis transaksi yang dipilih user di frontend
- `wilayah_id` wajib dikirim untuk semua endpoint create draft order frontend
- backend akan lookup rule ongkir frontend berdasarkan kombinasi `team_id` dan `wilayah_id`
- jika rule cocok, backend otomatis menambah 1 line produk ongkir dengan harga mengikuti pricelist atau `list_price` produk ongkir
- backend menghitung total berat dari seluruh line produk non-ongkir menggunakan field `weight` pada produk
- jika rule cocok, backend otomatis menambah 1 line produk ongkir dengan nominal `total_kg x tarif_per_kg`
- semua produk yang dipakai untuk perhitungan ongkir berbasis kilogram harus memiliki field `weight` yang terisi benar
- Terms and Conditions di backend disimpan pada field `note` di `sale.order`
- `customer-qr-by-id` cocok jika frontend hanya perlu string referensi QR
- `customer-qr-payload-by-id` cocok jika frontend ingin langsung render QR dan menyimpan metadata QR sekaligus dari `customer_id`
@@ -24,6 +24,7 @@ Buat 1 rule contoh:
- `Team Sales`: team yang dipakai frontend
- `Wilayah`: kecamatan yang mewakili kelompok petani/customer
- `Shipping Product`: produk biaya pengiriman
- `Tarif per Kg`: misalnya `1500`
## Payload Contoh
@@ -121,7 +122,8 @@ Saat request berhasil:
- field `frontend_kecamatan_id` terisi sesuai `wilayah_id`
- order line asli dari frontend tetap ada
- sistem otomatis menambahkan 1 line produk ongkir
- harga ongkir mengikuti pricelist order atau `list_price` produk ongkir
- nominal ongkir dihitung dari `total berat produk x tarif per kg`
- deskripsi line ongkir menampilkan ringkasan berat total dan tarif per kg
## Kasus Error Yang Perlu Diuji
@@ -129,6 +131,8 @@ Saat request berhasil:
- `wilayah_id` tidak valid
- kombinasi `team_id + wilayah_id` belum punya rule
- produk ongkir pada rule tidak valid atau `sale_ok = False`
- `Tarif per Kg` pada rule masih `0`
- semua produk pada order belum punya berat sehingga total kilogram = `0`
## Cek di Backend
@@ -42,6 +42,13 @@ class FrontendShippingRule(models.Model):
domain="[('sale_ok', '=', True)]",
ondelete="restrict",
)
shipping_price_per_kg = fields.Float(
string="Tarif per Kg",
required=True,
default=0.0,
digits="Product Price",
help="Tarif ongkos kirim yang akan dikalikan dengan total berat produk pada order frontend.",
)
_sql_constraints = [
(
@@ -51,16 +58,18 @@ class FrontendShippingRule(models.Model):
),
]
@api.depends("team_id", "wilayah_kecamatan_id", "shipping_product_id")
@api.depends("team_id", "wilayah_kecamatan_id", "shipping_product_id", "shipping_price_per_kg")
def _compute_name(self):
for rule in self:
parts = [rule.team_id.name, rule.wilayah_kecamatan_id.name, rule.shipping_product_id.display_name]
rule.name = " / ".join([part for part in parts if part])
@api.constrains("company_id", "team_id", "shipping_product_id")
@api.constrains("company_id", "team_id", "shipping_product_id", "shipping_price_per_kg")
def _check_company_consistency(self):
for rule in self:
if rule.team_id.company_id and rule.team_id.company_id != rule.company_id:
raise ValidationError(_("Team Sales company must match the shipping rule company."))
if rule.shipping_product_id.company_id and rule.shipping_product_id.company_id != rule.company_id:
raise ValidationError(_("Shipping product company must match the shipping rule company."))
if rule.shipping_price_per_kg <= 0:
raise ValidationError(_("Tarif per Kg must be greater than 0."))
@@ -159,17 +159,14 @@ class SaleOrder(models.Model):
vals["analytic_account_id"] = self.analytic_account_id.id
return vals
def _get_frontend_shipping_price(self, product, qty=1.0):
def _get_frontend_shipping_total_weight(self):
self.ensure_one()
pricelist = self.pricelist_id
if pricelist:
try:
return pricelist.with_context(uom=product.uom_id.id).get_product_price(
product, qty, self.partner_id
)
except Exception:
pass
return product.lst_price
total_weight = 0.0
for line in self.order_line:
if line.display_type or line.is_frontend_shipping_line or not line.product_id:
continue
total_weight += line.product_uom_qty * (line.product_id.weight or 0.0)
return total_weight
def _apply_frontend_shipping_rule(self):
for order in self:
@@ -200,17 +197,34 @@ class SaleOrder(models.Model):
raise UserError(
_("Shipping product '%s' is not available for sale.") % product.display_name
)
if rule.shipping_price_per_kg <= 0:
raise UserError(
_("Shipping rule for Team Sales '%s' and Wilayah '%s' must define Tarif per Kg greater than 0.")
% (order.team_id.name, order.frontend_kecamatan_id.name)
)
total_weight = order._get_frontend_shipping_total_weight()
if total_weight <= 0:
raise UserError(
_("Total product weight must be greater than 0 to calculate frontend shipping cost.")
)
taxes = product.taxes_id.filtered(
lambda tax: not tax.company_id or tax.company_id == order.company_id
)
price_unit = total_weight * rule.shipping_price_per_kg
line_name = _("%s\nBerat total: %.2f Kg x Tarif: %.2f") % (
product.get_product_multiline_description_sale() or product.display_name,
total_weight,
rule.shipping_price_per_kg,
)
line_vals = {
"order_id": order.id,
"product_id": product.id,
"name": product.get_product_multiline_description_sale() or product.display_name,
"name": line_name,
"product_uom_qty": 1.0,
"product_uom": product.uom_id.id,
"price_unit": order._get_frontend_shipping_price(product, qty=1.0),
"price_unit": price_unit,
"tax_id": [(6, 0, taxes.ids)],
"is_frontend_shipping_line": True,
}
@@ -11,6 +11,7 @@
<field name="team_id"/>
<field name="wilayah_kecamatan_id"/>
<field name="shipping_product_id"/>
<field name="shipping_price_per_kg"/>
</tree>
</field>
</record>
@@ -28,6 +29,7 @@
<field name="team_id"/>
<field name="wilayah_kecamatan_id"/>
<field name="shipping_product_id"/>
<field name="shipping_price_per_kg"/>
</group>
</sheet>
</form>
@@ -42,6 +44,7 @@
<field name="team_id"/>
<field name="wilayah_kecamatan_id"/>
<field name="shipping_product_id"/>
<field name="shipping_price_per_kg"/>
<field name="business_category_id"/>
<field name="company_id"/>
<filter string="Active" name="active" domain="[('active', '=', True)]"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB