feat(pricing): the server prices app orders with legacy's making-charge formula (MON-08)
Legacy: final % = variant % + customer % − the largest active promotion on the product, its category or its brand; rial charge 0. The apps sent the variant's own % only, so the new app undercharged by the customer's percent (usually 3) and ignored promotions, and the server trusted whatever the app sent. - pricing/wage-formula.ts: one formula, legacy by default, with coefficients, promotion pick, constant, floor/cap and rounding so the admin screen can change it; validated - an app order is priced by it; the app's wage, discount and rial charge are ignored; an operator's manual numbers are kept - POST customer/me/wage-quote; both apps show that figure - admin/pricing/wage-formula (config.wage): read, preview, approve with the sample it was checked on, last 20 versions kept; the setting is kept off the generic const routes Owner approval: 2026-10-08. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4b7b2473fc
commit
699657e483
@@ -0,0 +1,58 @@
|
||||
import { Body, Controller, Get, HttpCode, Post, Put } from '@nestjs/common';
|
||||
import { ArrayMaxSize, IsArray, IsString } from 'class-validator';
|
||||
import { CurrentAdmin, RequirePermission } from '../auth/guards';
|
||||
import type { AuthenticatedAdmin } from '../auth/jwt.strategy';
|
||||
import { CurrentCustomer, CustomerAuth } from '../customer-auth/customer.guards';
|
||||
import type { AuthenticatedCustomer } from '../customer-auth/customer-jwt.strategy';
|
||||
import { WageService } from './wage.service';
|
||||
|
||||
/**
|
||||
* «اجرت» admin screen — the making-charge formula, a worked sample, and the
|
||||
* approval that puts a new formula in force. Behind its own rid.
|
||||
*/
|
||||
@Controller('admin/pricing/wage-formula')
|
||||
@RequirePermission('config.wage')
|
||||
export class WageAdminController {
|
||||
constructor(private readonly wage: WageService) {}
|
||||
|
||||
@Get()
|
||||
get() {
|
||||
return this.wage.getConfig();
|
||||
}
|
||||
|
||||
/** Nothing is saved: the draft and the formula in force on the same sample. */
|
||||
@Post('preview')
|
||||
@HttpCode(200)
|
||||
preview(@Body() body: { formula?: unknown; sample?: unknown }) {
|
||||
return this.wage.preview(body?.formula, body?.sample);
|
||||
}
|
||||
|
||||
/** Puts the formula in force, recorded with the sample it was checked on. */
|
||||
@Put()
|
||||
approve(@Body() body: { formula?: unknown; sample?: unknown }, @CurrentAdmin() admin: AuthenticatedAdmin) {
|
||||
return this.wage.approve(body?.formula, body?.sample, admin);
|
||||
}
|
||||
}
|
||||
|
||||
class WageQuoteDto {
|
||||
@IsArray()
|
||||
@ArrayMaxSize(200)
|
||||
@IsString({ each: true })
|
||||
variantIds!: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The making charge the app shows on a product and in the cart — computed by
|
||||
* the same code that prices the order, for the signed-in customer.
|
||||
*/
|
||||
@Controller('customer/me/wage-quote')
|
||||
export class WageCustomerController {
|
||||
constructor(private readonly wage: WageService) {}
|
||||
|
||||
@CustomerAuth()
|
||||
@Post()
|
||||
@HttpCode(200)
|
||||
async quote(@Body() dto: WageQuoteDto, @CurrentCustomer() me: AuthenticatedCustomer) {
|
||||
return [...(await this.wage.quote(me.id, dto.variantIds)).values()];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user